# Fly.io Billing Source: https://docs.fly.io/about/billing Understand how Fly.io bills each organization monthly: Machine, volume and snapshot charges, invoices, unified billing, payment options, and account deletion. ## 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](/about/pricing) for details. *** ## View invoices You can view and download invoices from the **Billing** section of your [dashboard](https://fly.io/dashboard/personal/billing) for each organization. To download a past invoice, click **View** for the relevant cycle. You'll be sent to the Stripe billing portal where you'll have the option to download the invoice. *** ### Understand your invoice charges 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](mailto:billing@fly.io). *** ## Machine billing 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, read about [Compute pricing](/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. For example, a Machine described in your dashboard as having 1GB of rootfs stopped for 10 days in the entire month will cost \$0.05. If you have 3 stopped Machines of 1GB rootfs each stopped for 30 days it will cost \$0.45 (\$0.15 x 3). *** ## Volume billing Volume billing is pro-rated to the hour. For details, see [Volume pricing](/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. 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](/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). *** ## Unified Billing Unified billing allows you to consolidate billing for multiple organizations under a single organization. This means you'll receive a single invoice for all organizations under unified billing. This feature is useful for managing billing for multiple organizations, such as when you have multiple teams or projects. **Billing Organization**: The organization that is responsible for paying for the resources used by all organizations linked to it. Any organization in good standing with an attached payment method can be a Billing Organization. **Linked Organization**: An organization that is linked to another organization for billing purposes. It otherwise functions as a normal organization. ### How Unified Billing Works * You'll receive a single invoice for all organizations under unified billing. * Billing Organization must have a payment method on file (credit card or Stripe Link). * Linked Organizations share credits with their Billing Organization. * Billing Organizations can have a max of 100 Linked Organizations. ### How to use Unified Billing You can either create a new Linked Organization or link an existing one: #### New Organization A **Linked Organization** can be created from the [new organization page](https://fly.io/organizations/new). When creating a new organization, you can choose an existing Organization to link it to. #### 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 accounting purposes before linking an organization. *** ## Some things to consider * Each organization has its own [private network](/networking/private-networking). Organizations are isolated from each other, which might be advantageous from a security standpoint. * Cross organization networking is still possible with [Flycast](/networking/flycast) * Detaching a Linked Organization from its Billing Organization is not possible at this time. Instead we suggest creating a new organization and moving resources there. *** ## Payment options We process payments through Stripe. Monthly invoicing requires a credit card or credits. ### How we use credit cards We require an active, valid credit card on file for most Fly.io accounts to do things like deploying multiple apps and deploying public images. This is primarily a means to prevent abuse and ensure that we can collect payment at the end of the month. ### Pre-authorization Requests We may send a pre-authorization request to the issuing bank to verify a bank will authorize charges. Some credit card companies present these as real charges, which may surprise you. A pre-auth is really just a hold on a credit card; it is not a real charge. We’ll typically pre-authorize a small amount (usually less than US \$10) after signup, then cancel the authorization immediately. Banks may show the pre-auth for up to 10 business days, even though we have not collected any money. ### If you don't have a credit card While a credit card is the preferred payment method, if you don't have one, then you can add credits to your account. You can purchase credits from the Billing section of the [dashboard](https://fly.io/dashboard/personal/billing) in various amounts (minimum purchase \$25). Your usage cost is subtracted from the credit balance each month. ### Prepaid cards You can't use a prepaid card as a default (or saved) payment method. You can, however, use a prepaid card to add credits to your account. *** ## 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: * Delete or remove yourself from organizations to avoid leaving behind inaccessible orgs. This does not include your personal organization. * Delete apps (or remove yourself from the associated organizations). * Remove certificates so that any custom domains are released. * 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. Deleting your account removes your name, username, and email address from the account record and revokes your sign-in identities and API tokens. If the account was billed, invoices are retained as required by tax and accounting law. ### Deletion requests by email Fly.io can't process account or data deletion requests sent by email, including requests made under GDPR Article 17 or similar privacy laws. An email doesn't let us verify that the sender owns the account, so the self-service steps above are the only way to delete an account and the personal data attached to it. If you've lost access to your account, [reset your password](https://fly.io/app/password_resets/new) using the email address on the account. Newsletter email is managed separately from your account. To stop it, use the unsubscribe link at the bottom of any newsletter. It takes effect immediately and doesn't affect your account or service notifications. *** ## Legacy plans On a legacy plan and wondering how it's billed? Read more about [Discontinued Plans](/about/discontinued-plans). # Using Our Brand Source: https://docs.fly.io/about/brand Download Fly.io logos in color and monochrome landscape, portrait, and brandmark formats, and review the trademark usage agreement for using them. **The following guidelines will help you correctly make use of our brand.** By using our brand assets, you accept the terms of our [Trademark Usage Agreement](#trademark-usage-agreement) and understand that, if violated, your permission to use these materials will be revoked. ## *Color Landscape Logo* Our color landscape logo combines our brandmark and serif wordmark in a landscape format. This is our preferred logo, and we encourage you to use it whenever possible. ![The navy and purple Fly.io logo in landscape format on a white background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-landscape.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=d95f93624e77cfd10545ba051cddd823) [Download Color Landscape Logo](/images/brand/logo-landscape.zip) ![The inverted white and purple Fly.io logo in landscape format on a navy background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-landscape-inverted.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=3efeccd3abab47dd91622f5274a932d0) [Download Inverted Color Landscape Logo](/images/brand/logo-landscape-inverted.zip) ## *Monochrome Landscape Logo* The monochrome landscape logo is used to maximize clarity and contrast. Use it when the color version isn’t a good option, such as on a busy or low-contrast background. ![The monochrome navy Fly.io logo in landscape format on a white background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-landscape-dark.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=97b1695a92304089fc508cb29f6039bb) [Download Dark Landscape Logo](/images/brand/logo-landscape-dark.zip) ![The monochrome white Fly.io logo in landscape format on a navy background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-landscape-light.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=28b0d8b01ae9b548085ab044e94e3d37) [Download Light Landscape Logo](/images/brand/logo-landscape-light.zip) ## *Color Portrait Logo* Our color portrait logo combines our brandmark and serif wordmark in a portrait format. Use it when our landscape logo isn’t a good option, such as a tight layout where horizontal space is limited. ![The navy and purple Fly.io logo in portrait format on a white background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-portrait.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=b4a0578902681b4600e477d0ef9c9224) [Download Color Portrait Logo](/images/brand/logo-portrait.zip) ![The inverted white and purple Fly.io logo in portrait format on a navy background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-portrait-inverted.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=88a532c6484d761813f593c55b4b8e62) [Download Inverted Portrait Logo](/images/brand/logo-portrait-inverted.zip) ## *Monochrome Portrait Logo* The monochrome portrait logo is used to maximize clarity and contrast. Use it when the color version isn’t a good option, such as on a busy or low-contrast background. ![The monochrome navy Fly.io logo in portrait format on a white background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-portrait-dark.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=9fcf5d39ee29ec0d0bf4a6f3eb33eac9) [Download Dark Portrait Logo](/images/brand/logo-portrait-dark.zip) ![The monochrome white Fly.io logo in portrait format on a navy background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/logo-portrait-light.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=d238770c1948f146e9e5369180cd0ce8) [Download Light Portrait Logo](/images/brand/logo-portrait-light.zip) ## *Color Brandmark* Some circumstances call for our brandmark to stand on its own. This is typically reserved for extremely small dimensions, when space is limited, or where the brand is already established. ![The navy and white Fly.io brandmark on a purple and white background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/brandmark.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=27f7268231b0309e3c46dbbe3ff0e298) [Download Color Brandmark](/images/brand/brandmark.zip) ![The inverted navy and white Fly.io brandmark on a purple and navy background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/brandmark-inverted.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=92f280bcfc9352d17ad63d012bcfeab4) [Download Inverted Color Brandmark](/images/brand/brandmark-inverted.zip) ## *Monochrome Brandmark* The monochrome brandmark is used to maximize clarity and contrast. Use it when the color version isn’t a good option, such as on a busy or low-contrast background. ![The monochrome navy Fly.io brandmark on a white background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/brandmark-dark.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=7feb7ca386483c259e575579e530440c) [Download Dark Brandmark](/images/brand/brandmark-dark.zip) ![The monochrome white Fly.io brandmark on a navy background](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/brand/brandmark-light.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=09752b634f409b10da60b34e2d386f16) [Download Light Brandmark](/images/brand/brandmark-light.zip) ## *Trademark Usage Agreement* This agreement explains how you must use and present our brand assets at all times. Your use of our brand assets indicates your acceptance of this agreement, as well as your understanding that, if violated, your permission to use these materials will be revoked. * The Fly.io name and brand assets are trademarks of Fly.io, Inc. * Use of our brand assets must be expressly authorized in writing. * Our brand assets are provided for use in informational articles, video tutorials, and other related media only. You may not use our brand assets for t-shirts, stickers, or other merchandise at any time. * Your use must not mislead consumers as to our sponsorship of, affiliation with, or endorsement of your company or your products or services. * Our brand assets are our exclusive property. All goodwill that results from your use of our brand assets will be solely to our benefit. You will not take any action that is at odds with our rights or ownership of our brand assets. Where used on a web page, our brand assets, as well as the mention of our name, should include embedded hyperlinks to our homepage: [https://fly.io](https://fly.io). * Our brand assets must be used in a respectful manner. Our brand assets may not be used in a way that harms us, our products or services, or in a manner which, in our opinion, lessens or otherwise damages our reputation or the goodwill in our brand assets. In other words, please do not associate our brand assets with any illicit or illegal activities or use them in a way that is deceptive or harmful. # Cost Management on Fly.io Source: https://docs.fly.io/about/cost-management Predict your Fly.io bill: budget for always-on Machines, watch bandwidth, volumes, IPv4 and leftover managed services, and know free allowances don't cap it. ![Illustration by Annie Ruygt of a Frankie the hot air balloon tallying up a total on a paper-tape calculator](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/cost-management.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=82755961c847e1a1d3f0c2078eb835c4) ## 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. > [!NOTE] > > **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 \$23.95/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.62/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 **\$6.61–11.01/month**, worst-case around **\$22 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. > [!NOTE] > > **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](/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**](/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](/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.](/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](/about/pricing#volumes). * **Volume snapshots are billed.** Snapshot storage is billed on the data stored, after a free allowance each month, so automatic snapshots you keep for backups add to your bill. [See snapshot pricing](/about/pricing#volume-snapshots). * **Managed services live outside your apps.** Fly's [Managed Postgres](/postgres/index) (MPG), [Upstash Redis](/upstash/redis), and [Tigris object storage](/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](/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](/blueprints/autoscale-machines) A practical guide to getting basic autoscaling working with Fly Machines. Covers HTTP-based scaling triggers. * [Metrics-based Autoscaling](/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](/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. # Discontinued Plans Source: https://docs.fly.io/about/discontinued-plans Fly.io stopped selling Hobby, Launch, and Scale plans on October 7, 2024. See the legacy allowances still honored, pro-rated billing, and Pay As You Go. ## Overview Fly.io no longer offers plans to new customers. If you purchased a Launch or Scale plan before October 7, 2024, you can remain on those plans unless you convert to Pay As You Go, delete your payment method, or otherwise stop using Fly.io. This page covers what those discontinued plans include and how they're billed. If you're not on a legacy plan, see [Pricing](/about/pricing) and [Billing](/about/billing) instead. *** ## Legacy free 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. The following resources were included for free on the Hobby (deprecated), Launch, and Scale plans, and are still honored for any organizations that were on these plans before we sunset them: * Up to 3 shared-cpu-1x 256mb VMs * 3GB persistent volume storage (total) Outbound data transfer: * 100 GB North America & Europe * 30 GB Asia Pacific, Oceania & South America * 30 GB Africa & India If your plan includes 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. *** ## Legacy plan billing > [!NOTE] > > 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. *** ## Paid Hobby plan with Free Trial credit 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 (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** * [Fly.io Resource Pricing](/about/pricing) What Fly.io charges for compute, storage, and network today. * [Billing for Fly.io](/about/billing) How invoicing, payment methods, and usage tracking work. * [Cost Management](/about/cost-management) Best practices for estimating, monitoring, and controlling your spend. # Fly.io Extensions Program Source: https://docs.fly.io/about/extensions Partner with Fly.io as an extension provider: requirements for account and resource provisioning, SSO via flyctl ext, usage billing APIs, and hosting. Fly.io is a global cloud service for running full stack apps close to users. We run on our own hardware in [datacenters all over the world](/reference/regions). We have two main goals: to deliver a slick developer experience, and to build the best possible platform for running full stack apps. We're looking for partners that share this goal, extending Fly.io with services our customers need. Services such as managed databases, exception handlers, CI runners or log aggregators are great examples. Check out our [managed Upstash for Redis](/upstash/redis) to get an idea of what's possible, and how we integrate services directly into our CLI. [Check out our documentation](https://fly.io/docs) to see what our platform has to offer. Contact us at [extensions@fly.io](mailto:extensions@fly.io) to discuss your case! ## Provider Requirements As an extension provider, you should give us a way to: * Provision resources, user accounts and organizations * Allow single sign-on login into your UI * Fetch billing details on a daily basis so customers can stay up-to-date with metered costs Our [Extensions API documentation](/reference/extensions_api) outlines our API and SSO requirements in detail. ## Account provisioning Where possible, our organizations should map to your organizations, and our user accounts to yours. In both cases, we provide obfuscated addresses that route to individual customers or to organization administrators. ## Resource provisioning You should give us an API endpoint to provision extensions synchronously. This request should respond with unique resource ID, connection strings, tokens and other relevant information. ## Single sign on Customers should be able to login to your UI using a single command like \`flyctl ext logjam dashboard\`\`. This may be done by integrating with our OAuth authentication flow. Once logged in, customers should have access to your standard platform features. There may be details to iron out here, such as which privileges to assign to Fly.io-provisioned users. ## Billing We want you to be free to model your pricing as you see fit. But, for the best customer experience, Fly.io should ultimately be the one that charges a customer. Also, Pay-as-you-go pricing fits our model best, since it's how we bill. So you should run your own billing logic, and offer an API to provide up-to-date, per-account billing details. Contact us at [extensions@fly.io](mailto:extensions@fly.io) to discuss billing options. ## Deploying latency sensitive services on the Fly.io platform To fulfil our promise of a performant application in any Fly.io region, latency sensitive services — like Redis or MySQL — should be deployed on Fly.io. You'll also benefit from volume discounts on cloud resources and a growing number of [global points of presence](/reference/regions). Your Fly.io network is essentially a global, encrypted LAN, with DNS service discovery and load balancing built-in. This greatly simplifies configuration of services that cluster and gossip. Customers access your service via a single private IP address that automatically routes traffic to the nearest provider VM. We also offer cost-cutting features such as [automatic VM stop/start based on incoming request volume](/launch/autostop-autostart). Contact us at [extensions@fly.io](mailto:extensions@fly.io) to learn more - we're happy to help you get started! # Fly.io Free Trial Source: https://docs.fly.io/about/free-trial Check what the Fly.io free trial includes, its VM hour, Machine, volume and memory limits, how to track usage, and what happens when the trial ends. ![Illustration by Annie Ruygt of a Frankie the hot air balloon showing a Free Trial banner to his developer friend who carries an app](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/free-trial.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=b02c60bcc2211f005e1f6708adddc8e5) **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. > [!NOTE] > > 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 > [!NOTE] > > 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](/about/pricing) * [How Billing Works](/about/billing) * [Fly.io Pricing Calculator](https://fly.io/calculator) # Healthcare apps on Fly Source: https://docs.fly.io/about/healthcare See how Fly.io supports healthcare apps: SOC2 Type 2 audits, BAA signing, WireGuard encryption in transit, encrypted volumes at rest, 6PN private networking. ![Illustration by Annie Ruygt of Frankie the hot air balloon examining a green box with A written on it](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/healthcare.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=93b5e1b4954b3951bd9c5d1c616d9b14) Fly.io is a great place to develop and host healthcare applications! You can get started for free and be up and running with a fully secure solution in minutes. We recognize that healthcare apps and data are different beasts and we're here to protect your patients' data (and keep your auditors happy). We're SOC2 (Type 2) audited, we'll sign BAAs, and we're available to answer questions you might have about how our platform meets your compliance needs [(just ask!)](mailto:sales@fly.io). Nailing the security for a HIPAA-compliant application can be a big task, and no hosting provider can do it all for you. But Fly.io has a security-first design and a number of features that make HIPAA simpler: ## Access Control ### Database Endpoint Security Whether you’re running Fly.io Postgres or your own database, there’s no network ACLs required to ensure that only your application can talk to the database server; databases on Fly.io talk to app servers over 6PN and WireGuard, and never to the public Internet. ### Anti-Spoofing Controls Attackers that boot up evil Fly.io apps can’t spoof packets to other Fly.io instances; we use both Linux kernel controls, IP routing, and eBPF programs to make sure of that. It’s kind of a 1998 sort of attack to worry about, but in case your auditor cares, we took care of it. ## Audit ### Centralizing Logging and Metrics Fly.io collects logs and metrics from your apps, and can send them wherever you need them to go; you can get a single feed of logs from all your instances into an ELK cluster or a Splunk instance, or aim Grafana at our metrics, so you have visibility and an audit trail of what’s going on with your app. ## Integrity ### Hardened Hosting Apps running on Fly.io run inside Firecracker, a Rust-based, memory-safe KVM hypervisor designed at Amazon as the engine for Fargate. At Fly.io, we take container images from our users and transmogrify them into VMs, for full, no-shared-kernel isolation between applications. Firecracker VMs run as userland processes on our hosts, and are further locked down with modern Linux security tools, including cgroups, file and network namespaces, resource limits, and privilege separation. ### Kernel Vulnerability Response Fly.io is responsible for the security both of our host kernels (of course) and of the guest kernels our apps run in; one less thing for you worry about. ## Authentication ### Multi-Factor Authentication Fly.io supports standard multifactor authentication apps, because of course we do. We feel a little miffed you had to ask. ### Secrets Management It’s easy to expose secrets to your running apps without leaking them in configurations: we provide a “write-only” secrets management scheme that keeps app secrets encrypted, exposing them only to running instances of your app, using a token-based system that ensures your plaintext secrets never hit machines not running your apps. ## Confidentiality ### WireGuard Everywhere The Fly.io platform is knitted together out of hosts connected via a WireGuard mesh. Everything talks to everything else over WireGuard. WireGuard is a next-generation in-kernel (and userland) VPN designed by vulnerability researchers for simplicity, auditability, and modern cryptography. The Linux kernel implementation of WireGuard runs in steady state without requiring dynamic memory allocation! WireGuard is great, and is the gold standard for secure network transports. We run a full mesh of WireGuard. That means that once a request arrives at our edge from the Internet, when we proxy it to a host running your app, that communication occurs over a WireGuard virtual network. We also use WireGuard in our API. Need to SSH to an instance of your app? That’ll happen over WireGuard. Need to open a Postgres shell? WireGuard. Deploy a new instance using a remote builder running in Fly.io? You guessed it: the Docker protocol stuff is running over WireGuard, from your machine to our hosts. ### Encryption In Transit See above! WireGuard runs 256-bit ChaCha20-Poly1305 with an authenticated Curve25519 key exchange. ### TLS Everywhere Fly.io terminates TLS for our users at our edge. We run a fleet of memory-safe, Rust-based proxies that use the Hyper and Rustls libraries to implement HTTPS, in a tight configuration that scores an A grade from Qualys SSL Labs, without you needing to lift a finger. You can terminate TLS in your application instead of at our edge, if you really want to. But you won’t want to. ### Automatic Certificate Management with LetsEncrypt Fly.io manages the ACME protocol to securely provision LetsEncrypt TLS certificates, so that’s another thing you’re just not going to have to think much about. ### HTTP Strict Transport Security and HTTPS-Only It’s easy (a single configuration line) to lock your apps to HTTPS-only, using the HSTS protocol to direct browsers exclusively to the secure endpoint of your application and defeating SSL-stripping attacks. ### Default-Deny Public Networking Apps running on Fly.io get routable IPv6 addresses and shared IPv4 addresses. But when your users hit those addresses, they’re not bouncing directly off your app instances; instead, they’re routed to our edge, where we use our memory-safe Rust proxy to direct traffic. What that means for you is that nothing on your app is exposed unless you ask us to expose it. No security group rules or network ACLs required! You’re locked down by default. ### 6PN Private Networking Modern applications are often composed of ensembles of services. Some of those services are fit for talking to random users on the Internet. Others are best kept under wraps. Fly.io makes it easy to deploy complicated applications built out of multiple services: all Fly.io apps live in a private IPv6 network exclusive to your organization. We use eBPF in the Linux kernel to ensure that private networks can’t talk to each other; they’re completely private, without any extra configuration. We call this feature 6PN (for “IPv6 Private Network”), and it means there’s zero security lockdown work required to deploy a database, Redis cache, or background job scheduler. Your app server will be able to talk to them right away. Nobody outside your organization will be able to talk to them at all. ### Network Segregation “Network Segregation” is the term an enterprise IT administrator at a big bank would describe 6PN. Yup, your networks are segregated. ### Secure Network Architecture Sure, you could call 6PN that, too. Did we mention there are no security group rules to review? There are no security group rules to review. ### Encryption At Rest Databases like Fly.io Postgres are built on Fly.io Volumes, our persistent storage feature. It works like a drive plugged in and mounted in your app instance. And those drives are block-level encrypted with AES-XTS. We manage the keys for the drives for you, using a token-based orchestration system that ensures the keys are only accessible from privileged processes on hosts actually running your app. Check off another HITRUST CSF requirement from your list! ## Availability ### High Availability Scaling apps across geographic regions is, like, the whole point of the service? I think it’s the whole point? At any rate: it’s definitely a thing we do. ### DDoS Protection Fly.io isn’t a DDoS Protection provider. But our upstreams have sophisticated DDoS tools, including blackhole routing and traffic scrubbing, which get regular workouts. ### Rolling Deployments Confidently deploy your Fly.io apps without worrying that you’re going to break everything: we’ll do rolling deploys, with canaries and health-checks, so a known-good version of your app is always running. ### Advanced App Instance Recovery Space Modulator If your app crashes or exits, we’ll relaunch the VM. We have the technology. ### Off-Site Backups Anything stored persistently in a Fly.io volume is backed up on a regular schedule. ## More info Our blueprint for [Going to production with healthcare apps](/blueprints/going-to-production-with-healthcare-apps) 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 BAA, and deploying to production. You can also check our [community](https://community.fly.io) for answers to your questions. If you can't find what you're looking for or want to know more, [get in touch](mailto:sales@fly.io). # About Fly.io Source: https://docs.fly.io/about/index The About section of the Fly.io docs: pricing, billing, cost management, the free trial, support, healthcare controls, open source, brand, and legal terms. ![Illustration by Annie Ruygt of Frankie the hot air balloon holding an umbrella to protect a bird from the rain](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/accident-forgiveness.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=2d09fb64bb15d75e66c9ccd001d606cd) More details about using and working with the Fly.io platform. * **[Pricing](/about/pricing):** Pricing for compute, storage, and all the Fly.io platform services. * **[Billing](/about/billing):** Learn how billing works on Fly.io. * **[Cost Management](/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](/about/free-trial):** Find out about Fly.io's Free Trial. * **[Support](/about/support):** Who to contact and where to go when you need Fly.io support. * **[Healthcare](/about/healthcare):** Controls relevant to running healthcare apps on Fly.io. * **[Engineering Jobs and Hiring](/about/extensions):** Learn more about our hiring process. * **[Open Source](/about/open-source):** How we help those OSS projects that help us. * **[Using Our Brand](/about/brand):** Assets and guidelines to help represent our brand. * **[Privacy Policy](https://fly.io/legal/privacy-policy/):** Our policy on privacy, detailed here. * **[Terms of Service](https://fly.io/legal/terms-of-service/):** The legal terms of service for Fly.io users. # Fly.io Merchandise Source: https://docs.fly.io/about/merch Where to buy Fly.io merch: T-shirts, hoodies, stickers, and mugs with Frankie the balloon, sold through the Redbubble shop, and who to email for help. ![Fly.io Frankie Merchandise](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/frankie-merch.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=0c7f4e6dd5efd7035578ec74a73f60d5) ## 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). # Fly.io and Open Source Source: https://docs.fly.io/about/open-source How Fly.io supports the open source it depends on: equity grants to authors of projects like isolated-vm and acme-client, plus recurring sponsorships. ![](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/harbor-spot.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=1f8dcde2a5181a3e7a7c6fa84c502b2f) Fly.io builds on the work of a tremendous open source infrastructure community. We want open source authors to benefit from their work because we think a healthy, thriving open source ecosystem will help us build better products. ## Equity Grants There's a tradition of giving equity grants to startup advisors who help growing companies. We too give equity grants—with us, though, they go to open source authors whose projects we rely on such as [isolated-vm](https://github.com/laverdet/isolated-vm) and [acme-client](https://github.com/unixcharles/acme-client). Those projects are important to Fly.io and helped us build the developer experience we wanted. ## Recurring donations We sponsor the developers behind our favorite open source projects. Some we use heavily, like the [Hyper HTTP client for Rust](https://github.com/hyperium/hyper/). Some are just cool, like the [Gleam language](https://gleam.run). # Fly.io Resource Pricing Source: https://docs.fly.io/about/pricing See what Fly.io bills for, from Machines and volumes to Managed Postgres, data transfer, IPs, SSL, support plans and extensions, with sample app costs. ![Illustration by Annie Ruygt of Frankie the hot air balloon demonstrating a pricing chart on a whiteboard](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/pricing.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=9b865b44607c896bfcd26ee3316e766b)**Fly.io bills for the resources you use, with no monthly platform fee. The smallest always-on app costs \$2.19 per 30 days. A small web app with a Managed Postgres database costs about \$44 a month. The table below lists what we charge for, followed by a few examples of what real-ish apps cost.**## Everything we bill forPrices are in USD. Machine prices in this table are for Ashburn, Virginia (`iad`) and Secaucus, NJ (`ewr`), our lowest-priced regions. Machine prices vary by region; see [Started Fly Machines](#started-fly-machines) for the full list.Monthly Machine prices assume 30 days of continuous runtime. Machines are billed by the second while they're running, so an always-on Machine costs slightly more in a 31-day month.| What you pay for | Price | Notes | | - | - | - | | Started Machine, `shared-cpu-1x` with 256MB RAM | \$2.19/month | Smallest Machine size. Billed by the second while running | | Started Machine, `shared-cpu-1x` with 512MB RAM | \$3.69/month | | | Started Machine, `shared-cpu-1x` with 1GB RAM | \$6.70/month | | | Started Machine, `shared-cpu-2x` with 2GB RAM | \$13.39/month | | | Started Machine, `performance-1x` with 2GB RAM | \$33.00/month | | | Started Machine, `performance-2x` with 4GB RAM | \$66.00/month | | | Additional Machine RAM | \$6.00/GB/month | RAM added beyond the amount included with the CPU preset | | Stopped or suspended Machine | \$0.15/GB/month of rootfs | Only the Machine's root file system is billed while it's stopped | | Volume | \$0.15/GB/month | Based on provisioned capacity, including while detached or attached to a stopped Machine | | Volume snapshots | \$0.08/GB/month | First 10GB free each month. Based on data stored, not provisioned volume size | | Dedicated IPv4 address | \$2/month | Optional. Apps get a shared IPv4 address and IPv6 addresses at no charge | | Static egress IP | \$0.005/hour (\~\$3.60/month) | Optional | | SSL certificates | \$0.10/month per single hostname; \$1/month per wildcard | First 10 single-hostname certificates are free | | Outbound data to the internet | \$0.02/GB in North America and Europe \$0.04/GB in Asia Pacific, Oceania, and South America \$0.12/GB in Africa and India | Inbound data is free | | Private network data between regions | \$0.006/GB in North America and Europe \$0.015/GB in Asia Pacific, Oceania, and South America \$0.050/GB in Africa and India | Data transfer within a region is free. Different rates apply to some older organizations; see [data transfer pricing](#data-transfer-pricing) | | Managed Postgres | \$38/month (Basic) to \$1,922/month (Performance) | Storage is \$0.28/GB/month based on usage. See [Managed Postgres pricing](/postgres#pricing) | | Support | \$29/month Standard; \$199/month Premium; Enterprise starts at \$2,500/month | Optional. Community support is free | | Fly Kubernetes | \$75/month per cluster | Compute and volumes are billed separately | | Extensions such as Tigris and Upstash | Provider's list price | Pricing is set by the extension provider and charges appear on your Fly.io invoice |New organizations use Pay As You Go pricing. There's no monthly platform fee; you pay for the resources and services you use.## What does a typical app cost?Here are three example apps and what they'd cost per month. We use Ashburn (`iad`) for the examples unless otherwise noted, and assume the Machines run continuously. Traffic and storage marked "assumed" are example workloads, not included allowances. You can use the [pricing calculator](https://fly.io/calculator/) to estimate costs for your own app.If your Machines [auto-stop](/launch/autostop-autostart) when they're idle, your compute costs can be lower. You pay for the time a Machine is running and for its rootfs while it's stopped. If the Machine runs all month, you'll pay the full always-on cost shown here. See [Cost Management](/about/cost-management) for more ways to estimate and control your bill.### A small web app with Managed Postgres| Item | Monthly cost | | - | -: | | One `shared-cpu-1x` 512MB Machine, running continuously | \$3.69 | | 10GB volume | \$1.50 | | Managed Postgres, Basic plan | \$38.00 | | 1GB of Managed Postgres storage (assumed) | \$0.28 | | Volume snapshots, within the 10GB free allowance (assumed) | \$0.00 | | 10GB outbound data to the internet from North America (assumed) | \$0.20 | | **Total** | **\$43.67** |The app and database run in the same region, so data transfer between them is free. The app also uses a shared IPv4 address, which is free.In this example, Managed Postgres accounts for most of the bill. The app Machine and its 10GB volume cost \$5.19 per month.### A Discord bot or background worker running 24/7| Item | Monthly cost | | - | -: | | One `shared-cpu-1x` 256MB Machine, running continuously | \$2.19 | | 1GB outbound data to the internet from North America (assumed) | \$0.02 | | **Total** | **\$2.21** |This example doesn't use a volume, database, or dedicated IPv4 address, so there's nothing else to pay for. If the worker needs more memory, a `shared-cpu-1x` Machine with 512MB costs \$3.69 per month when running continuously.### An app running in three regionsSuppose an app runs one 1GB Machine each in Ashburn (`iad`), Frankfurt (`fra`), and Singapore (`sin`). Each Machine serves 50GB of traffic to users and sends 10GB of private network traffic to Machines in other regions during the month.| Item | Monthly cost | | - | -: | | One `shared-cpu-1x` 1GB Machine in Ashburn (`iad`) | \$6.70 | | One `shared-cpu-1x` 1GB Machine in Frankfurt (`fra`) | \$7.73 | | One `shared-cpu-1x` 1GB Machine in Singapore (`sin`) | \$8.50 | | 100GB outbound data from `iad` and `fra` at \$0.02/GB (assumed) | \$2.00 | | 50GB outbound data from `sin` at \$0.04/GB (assumed) | \$2.00 | | 20GB cross-region private network data from `iad` and `fra` at \$0.006/GB (assumed) | \$0.12 | | 10GB cross-region private network data from `sin` at \$0.015/GB (assumed) | \$0.15 | | **Total** | **\$27.20** |Machine prices vary by region, which accounts for the different compute costs above.Cross-region private network traffic is billed in both directions. The request is billed at the rate for the region it leaves, and the response at the rate for the region it leaves. So if a small request returns a large response, most of the cost is at the responding region's rate.This example uses granular data transfer pricing. Organizations created before July 18, 2024 that haven't switched to granular pricing pay the public internet rate for cross-region traffic instead. See [data transfer pricing](#data-transfer-pricing).## Is there a free tier?No. New organizations don't have a free tier or a monthly free usage allowance.New accounts do get a [free trial](/about/free-trial), which lasts for up to 2 hours of Machine runtime or 7 days, whichever comes first. You don't need a credit card to start the trial. After the trial ends, you'll need to add a credit card to keep using billable resources.The cheapest always-on app is a `shared-cpu-1x` Machine with 256MB of RAM in Ashburn (`iad`) or Secaucus (`ewr`). It costs \$2.19 per 30 days of continuous runtime, plus any outbound data transfer (\$0.02/GB in North America and Europe).Some resources and allowances are free even after the trial ends. These include the first 10GB of volume snapshot storage each month, the first 10 single-hostname SSL certificates, inbound data transfer, shared IPv4 addresses, and IPv6 addresses.Organizations on plans discontinued in October 2024 keep the free allowances included with those plans. See [Discontinued Plans](/about/discontinued-plans).## How it worksFly.io services are billed per organization, with [Linked Organizations](/about/billing#unified-billing) reporting resource usage to their parent Billing Organization. Plans get complicated, so we just charge based on usage. Pick and choose which pieces you need for your application; that's all you'll see on your invoice.Organizations are administrative entities on Fly.io that let you add members, share app development environments, and manage billing. [Billing](/about/billing) is based on the resources provisioned for your apps, pro-rated for the time they are provisioned.Organizations may be subject to automated scaling limits to prevent abuse or to help with capacity planning. Email the address in the error message if you run into such a limit and it's getting in your way.All organizations (except for Linked Organizations) require a [credit card](/about/billing#payment-options) on file.## ComputeWe charge for started and stopped Machines differently. For more details about how costs are calculated, see [Machine billing](/about/billing#machine-billing). To understand the difference between `performance` and `shared` CPU types in Machines, see [CPU performance](/machines/cpu-performance).### Started Fly Machines Region: The price of a running [Fly Machine](/machines) VM is the price of a named CPU/RAM preset, plus about per 30 days per GB of additional RAM. Here’s the pricing for named presets and a few standard additional RAM configurations: \ \ \ \ \ \ \
PresetCPU(s)RAMPrice/secondPrice/hourPrice/month
### Stopped Fly MachinesFor stopped Machines we charge only for the root file system (rootfs) needed for each Machine. Each 1GB of rootfs for a Machine stopped for 30 days is \$0.15. The amount of rootfs needed is defined by your OCI image generated on your app plus a few [containerd](https://containerd.io/) tweaks on the underlying file system.### Machine reservation blocksYou'll get a 40% discount when you reserve a block of compute time, either for `performance` Machines or `shared` Machines, in a specific region. Reservations apply to any number of Machines of the specified CPU class, in the specified region, in any of your organization's apps.The available reservation sizes are:**Performance Machines**- \$144/year for \$20/month of usage - \$1,440/year for \$200/month of usage - \$14,400/year for \$2,000/month of usage**Shared Machines**- \$36/year for \$5/month of usage - \$360/year for \$50/month of usage - \$3,600/year for \$500/month of usageYou pay the "per year" amount upfront, and each month receive a credit worth the "per month" amount. The credit does not rollover; it's only valid for the month in which it's granted. The credit applies only to CPU and additional RAM charges.There's no limit on the number or combinations of blocks that can be purchased. Reservations are backdated to the first day of the month in which they're purchased.For example, if you purchase a \$36/year `shared` Machines block in `cdg`, you'll pay \$36 upfront and receive \$5/month of credits applicable to `shared` Machines in `cdg` for 12 months, starting with the month of the purchase. Amortised over 12 months, the \$36 upfront cost is \$3/month, which is a 40% discount on the \$5/month of credits you receive.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.## Managed PostgresThe price of running Fly.io Managed Postgres depends on your selected Managed Postgres Plan and the amount of storage your databases use.Current pricing for Managed Postgres plans and storage is available [here](/postgres#pricing).> [!WARNING] > > **Important:** Managed Postgres lives outside your apps. Deleting an app won’t delete its database. Have a look in your Dashboard when you're cleaning up. A quick check can save you a surprise charge later.## Persistent Storage Volumes### Volumes[Fly Volumes](/volumes) are local persistent storage for Machines.- \$0.15/GB per month of provisioned capacity[Volume billing](/about/billing#volume-billing) is pro-rated to the hour.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- \$0.08/GB per month - First 10GB free each month[Volume Snapshot billing](/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](/volumes/snapshots#set-or-change-the-snapshot-retention-period) or [disabled](/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.You can check your snapshot usage in the **Billing** section of the [dashboard](https://fly.io/dashboard/personal/billing), on your Upcoming Invoice and in the Cost Explorer.## Network prices### Anycast IP addressesEach application receives a [shared IPv4 address](/networking/services#shared-ipv4) and unlimited [Anycast IPv6](/networking/services#ipv6) addresses for global load balancing.Dedicated IPv4 addresses are \$2/mo.### Managed SSL certificatesWe use Let's Encrypt to issue certificates, and donate half of our SSL fees to them at the end of each calendar year.- Single hostname certificates: \$0.10/mo - Wildcard certificates: \$1/moEvery organization's first 10 single hostname certificates are free.### Data transfer pricingWe bill for data leaving your app destined for the public internet or for apps or Machines in other regions, including:- 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 RedisThe 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)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.> [!NOTE] > > **Important:** Organizations created after July 18 2024 are automatically opted-in to use the granular data transfer rates and are billed at a different rate for private network data transfer between regions, per the following table. Organizations not using granular data transfer rates are billed for all data transfer (excluding that listed as free above) at the "Egress to public internet" rate.| Region groups | Egress to public internet cost | Private network cross-region transfer cost | | - | - | - | | - North America - Europe | \$0.02 per GB | \$0.006 per GB | | - 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 to using the non-granular data transfer rates once you opt in.### Static Egress IPs for MachinesStatic 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.- \$0.005 per hour (\~\$3.60/month) - 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](/about/support).## Fly Kubernetes[Fly Kubernetes](/kubernetes) (FKS) is a managed Kubernetes service that runs on Fly.io.- \$75/month per cluster - Plus the cost of [compute](#compute) and [Fly volumes](#persistent-storage-volumes) that you create## ExtensionsFly.io offers managed services operated by third parties, such as [Tigris Object Storage](/tigris) and [Upstash Redis](/upstash/redis).When you provision their services, you become their customer, and you pay their list prices via your monthly Fly.io bill. Charges are updated daily in your Fly.io dashboard.You will not be billed separately for:- Machines running the services, which are hosted in the provider's account - IP addresses associated with the serviceYou **will** be billed separately for data transfer to these external third-party services, including Tigris Object Storage. See our [data transfer pricing](#data-transfer-pricing) for details.## Unsupported Products### Unmanaged Fly Postgres (Unsupported)[Fly Postgres](/unmanaged-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](/unmanaged-postgres/managing/scaling) to suit your workload at any time.## Legacy plansOn a legacy plan and wondering what that includes? Read more about [Discontinued Plans](/about/discontinued-plans).## **Related reading**- [Billing for Fly.io](/about/billing) How invoicing, payment methods, and usage tracking work. - [Cost Management](/about/cost-management) Best practices for estimating, monitoring, and controlling your spend. - [Free Trial](/about/free-trial) What the Fly.io Free trial gives you and when billing begins. - [Organization Roles & Permissions](/security/org-roles-permissions) How org structure, permissions and billing interplay. - [Optimize Compute Costs: Fine‑tune your app](/apps/fine-tune-apps) Tuning machine size, memory/CPU, and stop‑/start behavior to reduce waste. # Support Source: https://docs.fly.io/about/support Get help with Fly.io through the community forum, billing email, paid email support and Support Portal, and see what to include in a ticket and what's in scope. ![Illustration by Annie Ruygt of Frankie the hot air balloon wearing a fireman hat, putting a ladder on a tree to save a cat](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/help.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=b53d140be966ba2aeb2efb323edbbc30) Need support? We can help! Here's where to look. ## Fly.io status For platform-wide issues and updates, please view our [status page](https://status.flyio.net/). For host-level issues that might affect your apps, see your [dashboard](https://fly.io/dashboard). ## Community forum **Who can use this:** Everyone **Use this for:** General questions, troubleshooting advice, best practices, and sharing tips with other Fly.io users. All customers have access to our active [community forum](https://community.fly.io). We're in the forums regularly to help out with customer issues and post new feature announcements. 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. ## Billing and account support **Who can use this:** Everyone **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. Our Standard, Premium, and Enterprise packages have access to email support. These plans come with an organization-specific address for emailing support questions, typically `@support.fly.io`. 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 **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. ## Managed Postgres Support We now offer [Managed Postgres (MPG)](/postgres/index), 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. 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](/unmanaged-postgres) app for now. ## Support metrics The Fly.io Support team publishes our email support metrics to better set expectations regarding response times and help to you understand how we're doing. You can view these metrics in the public [Support Metrics Dashboard](https://fly.io/support). ## Working with support We understand that describing technical issues can sometimes be challenging. Providing a clear and detailed description of the problem, including any noticeable symptoms and relevant artifacts, can help us identify similar cases and potentially speed up the resolution process. Here are some things to include in your ticket: * Your app name and/or Machine ID. * Specific errors in your app logs or browser. What you observed (e.g. 'it threw a 500 error'). * What you were trying to achieve (e.g. deploy the application). * What you did (e.g. I ran `fly deploy`). * UTC timestamps for when the issue occurred. * Prepending `LOG_LEVEL=debug` to any flyctl command will provide insight into the lower-level API calls being made. * If you are sending us error messages or log output, it's best to send that as plain text in the body of the email (or upload a `.txt` or `.log` file for longer outputs), rather than attached screenshots of your terminal window. ## Scope of Support This section highlights the products we actively work on and maintain, and therefore, the ones we are able to provide support for. While we can't offer direct support for your own code or the specific language or framework you use, here are some [guides to the most common frameworks](/getting-started/get-started-by-framework) used on Fly.io. > **Supported Products** > > * **Networking** > * **Machines** > * **Managed Postgres** (MPG) > * **Apps** > * **Launch/Deploy** (UI & CLI) > * **Volumes** > * **FKS** > * **Security** > * **Accounts & Billing** > * **Extensions** (Tigris, Upstash, Depot) > * **Monitoring** (metrics and logs) > **Not Supported** > > * **LiteFS** > * **Unmanaged Postgres** # Assign new IP address to app Source: https://docs.fly.io/api/machines/apps/assign-new-ip-address-to-app /api/machines/openapi.json post /v1/apps/{app_name}/ip_assignments # Create App Source: https://docs.fly.io/api/machines/apps/create-app /api/machines/openapi.json post /v1/apps Create an app with the specified details in the request body. # Create App deploy token Source: https://docs.fly.io/api/machines/apps/create-app-deploy-token /api/machines/openapi.json post /v1/apps/{app_name}/deploy_token # Destroy App Source: https://docs.fly.io/api/machines/apps/destroy-app /api/machines/openapi.json delete /v1/apps/{app_name} Delete an app by its name. # Get App Source: https://docs.fly.io/api/machines/apps/get-app /api/machines/openapi.json get /v1/apps/{app_name} Retrieve details about a specific app by its name. # List Apps Source: https://docs.fly.io/api/machines/apps/list-apps /api/machines/openapi.json get /v1/apps List all apps with the ability to filter by organization slug. # List IP assignments for app Source: https://docs.fly.io/api/machines/apps/list-ip-assignments-for-app /api/machines/openapi.json get /v1/apps/{app_name}/ip_assignments # Remove IP assignment from app Source: https://docs.fly.io/api/machines/apps/remove-ip-assignment-from-app /api/machines/openapi.json delete /v1/apps/{app_name}/ip_assignments/{ip} # Machines API overview Source: https://docs.fly.io/api/machines/index Base URL, Bearer token auth, and OpenAPI spec for the Fly.io Machines API, with links to Apps, Machines, Volumes, Secrets, and Certificates endpoints. This site hosts the API reference generated from the Fly.io Machines API OpenAPI specification. For how to get started, deeper explanations of each endpoint, and examples, visit the complete [Machines API docs](/machines/api/). Download the [OpenAPI spec](https://docs.fly.io/api/machines/openapi.json) to generate a client or explore the API in another tool. ## Base URL All requests are made against: ``` https://api.machines.dev ``` ## Authentication Authenticate every request with a Fly.io access token using the `Authorization: Bearer ` header. Generate one with [`fly tokens create`](/flyctl/cmd/fly_tokens_create) or the [Tokens](https://fly.io/tokens) page on the dashboard. ```bash theme={null} curl https://api.machines.dev/v1/apps?org_slug=personal \ -H "Authorization: Bearer $FLY_API_TOKEN" ``` ## Resources > **[Apps](/api/machines/apps/list-apps)** > > Group your Machines into apps. > **[Machines](/api/machines/machines/list-machines)** > > Launch and control fast-booting VMs. > **[Volumes](/api/machines/volumes/list-volumes)** > > Persistent storage for your Machines. > **[Postgres Clusters](/api/machines/postgres-clusters/list-postgres-clusters)** > > Create and manage Postgres clusters. > **[Secrets](/api/machines/secrets/list-secret-keys-belonging-to-an-app)** > > App secrets and encryption keys. > **[TLS Certificates](/api/machines/tls-certificates/list-certificates-for-app)** > > Certificates for custom hostnames. > **[Tokens](/api/machines/tokens/authenticate-token-header)** > > Authenticate and mint access tokens. # Cordon Machine Source: https://docs.fly.io/api/machines/machines/cordon-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/cordon “Cordoning” a Machine refers to disabling its services, so the Fly Proxy won’t route requests to it. In flyctl this is used by blue/green deployments; one set of Machines is started up with services disabled, and when they are all healthy, the services are enabled on the new Machines and disabled on the old ones. # Create Lease Source: https://docs.fly.io/api/machines/machines/create-lease /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/lease Create a lease for a specific Machine within an app using the details provided in the request body. Machine leases can be used to obtain an exclusive lock on modifying a Machine. # Create Machine Source: https://docs.fly.io/api/machines/machines/create-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines Create a Machine within a specific app using the details provided in the request body. **Important**: This request can fail, and you’re responsible for handling that failure. If you ask for a large Machine, or a Machine in a region we happen to be at capacity for, you might need to retry the request, or to fall back to another region. If you’re working directly with the Machines API, you’re taking some responsibility for your own orchestration! # Delete Metadata Source: https://docs.fly.io/api/machines/machines/delete-metadata /api/machines/openapi.json delete /v1/apps/{app_name}/machines/{machine_id}/metadata/{key} Delete metadata for a specific Machine within an app by providing a metadata key. # Destroy Machine Source: https://docs.fly.io/api/machines/machines/destroy-machine /api/machines/openapi.json delete /v1/apps/{app_name}/machines/{machine_id} Delete a specific Machine within an app by Machine ID. Only a Machine in the `stopped`, `suspended`, `failed`, `pending` or `archived` state can be deleted. Deleting a Machine in any other state, such as `started`, fails with `412 Precondition Failed` unless you set `force=true`, which kills the Machine first. Deleting a Machine that is already `destroying` or `destroyed` succeeds without doing anything. If someone else holds a lease on the Machine, the request fails with `409 Conflict`. Pass the lease nonce in the `fly-machine-lease-nonce` header to delete a Machine you hold the lease on. # Execute Command Source: https://docs.fly.io/api/machines/machines/execute-command /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/exec Execute a command on a specific Machine and return the raw command output bytes. # Get Lease Source: https://docs.fly.io/api/machines/machines/get-lease /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/lease Retrieve the current lease of a specific Machine within an app. Machine leases can be used to obtain an exclusive lock on modifying a Machine. # Get Machine Source: https://docs.fly.io/api/machines/machines/get-machine /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id} Get details of a specific Machine within an app by the Machine ID. # Get Machine Memory Source: https://docs.fly.io/api/machines/machines/get-machine-memory /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/memory Get current memory limit and available capacity for a machine # Get Metadata Source: https://docs.fly.io/api/machines/machines/get-metadata /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/metadata Retrieve metadata for a specific Machine within an app. # Get Metadata Value Source: https://docs.fly.io/api/machines/machines/get-metadata-value /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/metadata/{key} Get the value of a specific metadata key # List Events Source: https://docs.fly.io/api/machines/machines/list-events /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/events List all events associated with a specific Machine within an app. # List Machines Source: https://docs.fly.io/api/machines/machines/list-machines /api/machines/openapi.json get /v1/apps/{app_name}/machines List all Machines associated with a specific app, with optional filters for including deleted Machines and filtering by region. # List Processes Source: https://docs.fly.io/api/machines/machines/list-processes /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/ps List all processes running on a specific Machine within an app, with optional sorting parameters. # List Versions Source: https://docs.fly.io/api/machines/machines/list-versions /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/versions List all versions of the configuration for a specific Machine within an app. # Reclaim Machine Memory Source: https://docs.fly.io/api/machines/machines/reclaim-machine-memory /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/memory/reclaim Trigger the balloon device to reclaim memory from a machine # Release Lease Source: https://docs.fly.io/api/machines/machines/release-lease /api/machines/openapi.json delete /v1/apps/{app_name}/machines/{machine_id}/lease Release the lease of a specific Machine within an app. Machine leases can be used to obtain an exclusive lock on modifying a Machine. # Restart Machine Source: https://docs.fly.io/api/machines/machines/restart-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/restart Restart a specific Machine within an app, with an optional timeout parameter. # Set Machine Memory Limit Source: https://docs.fly.io/api/machines/machines/set-machine-memory-limit /api/machines/openapi.json put /v1/apps/{app_name}/machines/{machine_id}/memory Set the memory limit for a machine using the balloon device # Signal Machine Source: https://docs.fly.io/api/machines/machines/signal-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/signal Send a signal to a specific Machine within an app using the details provided in the request body. # Start Machine Source: https://docs.fly.io/api/machines/machines/start-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/start Start a specific Machine within an app. # Stop Machine Source: https://docs.fly.io/api/machines/machines/stop-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/stop Stop a specific Machine within an app, with an optional request body to specify signal and timeout. # Suspend Machine Source: https://docs.fly.io/api/machines/machines/suspend-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/suspend Suspend a specific Machine within an app. The next start operation will attempt (but is not guaranteed) to resume the Machine from a snapshot taken at suspension time, rather than performing a cold boot. # Uncordon Machine Source: https://docs.fly.io/api/machines/machines/uncordon-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/uncordon “Cordoning” a Machine refers to disabling its services, so the Fly Proxy won’t route requests to it. In flyctl this is used by blue/green deployments; one set of Machines is started up with services disabled, and when they are all healthy, the services are enabled on the new Machines and disabled on the old ones. # Update Machine Source: https://docs.fly.io/api/machines/machines/update-machine /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id} Update a Machine's configuration using the details provided in the request body. # Update Metadata (deprecated PUT alias of PATCH) Source: https://docs.fly.io/api/machines/machines/update-metadata-deprecated-put-alias-of-patch /api/machines/openapi.json put /v1/apps/{app_name}/machines/{machine_id}/metadata Deprecated: use `PATCH` on the same path, which this is identical to. + Despite the method, this does not replace the metadata: keys that are not in the request are left untouched. Null values and empty strings remove keys. + If `machine_version` is provided and no longer matches the current machine version, returns 412 Precondition Failed. # Update Metadata (set/remove multiple keys) Source: https://docs.fly.io/api/machines/machines/update-metadata-setremove-multiple-keys /api/machines/openapi.json patch /v1/apps/{app_name}/machines/{machine_id}/metadata Update multiple metadata keys at once. Keys that are not in the request are left untouched. Null values and empty strings remove keys. + If `machine_version` is provided and no longer matches the current machine version, returns 412 Precondition Failed. # Upsert Metadata Key Source: https://docs.fly.io/api/machines/machines/upsert-metadata-key /api/machines/openapi.json post /v1/apps/{app_name}/machines/{machine_id}/metadata/{key} Update metadata for a specific machine within an app by providing a metadata key. # Wait for State Source: https://docs.fly.io/api/machines/machines/wait-for-state /api/machines/openapi.json get /v1/apps/{app_name}/machines/{machine_id}/wait Wait for a Machine to reach a specific state. Specify the desired state with the state parameter. See the [Machine states table](https://fly.io/docs/machines/working-with-machines/#machine-states) for a list of possible states. The default for this parameter is `started`. This request will block for up to 60 seconds. Set a shorter timeout with the timeout parameter. # Models Source: https://docs.fly.io/api/machines/models Schema reference for every object in the Fly Machines API. These are the data models used across the Machines API requests and responses, generated from the OpenAPI specification. ## AcmeChallenge | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | | | `target` | `string` | No | | ## App | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | No | | | `internal_numeric_id` | `integer` | No | | | `machine_count` | `integer` | No | | | `name` | `string` | No | | | `network` | `string` | No | | | `network_cidr` | `string` | No | | | `organization` | `AppOrganizationInfo` | No | | | `status` | `string` | No | | | `volume_count` | `integer` | No | | ## AppOrganizationInfo | Property | Type | Required | Description | | - | - | - | - | | `internal_numeric_id` | `integer` | No | | | `name` | `string` | No | | | `slug` | `string` | No | | ## AppSecret | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `string` | No | | | `digest` | `string` | No | | | `name` | `string` | No | | | `updated_at` | `string` | No | | | `value` | `string` | No | | ## AppSecrets | Property | Type | Required | Description | | - | - | - | - | | `secrets` | `AppSecret[]` | No | | ## AppSecretsUpdateRequest | Property | Type | Required | Description | | - | - | - | - | | `values` | `map` | No | | ## AppSecretsUpdateResp | Property | Type | Required | Description | | - | - | - | - | | `Version` | `integer` | No | DEPRECATED | | `secrets` | `AppSecret[]` | No | | | `version` | `integer` | No | The app's secrets version after this change. Pass it as `min_secrets_version` when creating or updating Machines so that they are guaranteed to see the change. | ## assignIPRequest | Property | Type | Required | Description | | - | - | - | - | | `network` | `string` | No | | | `org_slug` | `string` | No | | | `region` | `string` | No | | | `service_name` | `string` | No | | | `type` | `IPAssignmentType` | No | | ## AssignIPResponse | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `string` | No | | | `egress` | `boolean` | No | | | `ip` | `string` | No | | | `ip_pair` | `IPPair` | No | ip\_pair is returned when "egress-pair" IP type is requested; in this case, ip is null. | | `network` | `IPAssignmentNetwork` | No | | | `region` | `string` | No | | | `service_name` | `string` | No | | | `shared` | `boolean` | No | | ## authenticateTokenRequest | Property | Type | Required | Description | | - | - | - | - | | `header` | `string` | No | | ## authorizeResponse | Property | Type | Required | Description | | - | - | - | - | | `access` | `flyio.Access` | No | | | `verified_token` | `root.VerifiedToken` | No | | ## authorizeTokenRequest | Property | Type | Required | Description | | - | - | - | - | | `access` | `main.TokenAccess` | No | | | `header` | `string` | No | | ## CertificateCheckResponse | Property | Type | Required | Description | | - | - | - | - | | `acme_requested` | `boolean` | No | | | `certificates` | `CertificateEntry[]` | No | | | `configured` | `boolean` | No | | | `dns_provider` | `string` | No | | | `dns_records` | `DNSRecords` | No | | | `dns_requirements` | `DNSRequirements` | No | | | `hostname` | `string` | No | | | `rate_limited_until` | `string` | No | | | `status` | `string` | No | | | `validation` | `CertificateValidation` | No | | | `validation_errors` | `CertificateValidationError[]` | No | | ## CertificateDetail | Property | Type | Required | Description | | - | - | - | - | | `acme_requested` | `boolean` | No | | | `certificates` | `CertificateEntry[]` | No | | | `configured` | `boolean` | No | | | `dns_provider` | `string` | No | | | `dns_requirements` | `DNSRequirements` | No | | | `hostname` | `string` | No | | | `rate_limited_until` | `string` | No | | | `status` | `string` | No | | | `validation` | `CertificateValidation` | No | | | `validation_errors` | `CertificateValidationError[]` | No | | ## CertificateEntry | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `string` | No | | | `expires_at` | `string` | No | | | `issued` | `IssuedCertificate[]` | No | | | `issuer` | `string` | No | | | `source` | `string` | No | Enum: custom, fly | | `status` | `string` | No | Enum: active, pending\_ownership, pending\_validation | ## CertificateSummary | Property | Type | Required | Description | | - | - | - | - | | `acme_alpn_configured` | `boolean` | No | | | `acme_dns_configured` | `boolean` | No | | | `acme_http_configured` | `boolean` | No | | | `acme_requested` | `boolean` | No | | | `configured` | `boolean` | No | | | `created_at` | `string` | No | | | `dns_provider` | `string` | No | | | `has_custom_certificate` | `boolean` | No | | | `has_fly_certificate` | `boolean` | No | | | `hostname` | `string` | No | | | `ownership_txt_configured` | `boolean` | No | | | `status` | `string` | No | | | `updated_at` | `string` | No | | ## CertificateValidation | Property | Type | Required | Description | | - | - | - | - | | `alpn_configured` | `boolean` | No | | | `dns_configured` | `boolean` | No | | | `http_configured` | `boolean` | No | | | `ownership_txt_configured` | `boolean` | No | | ## CertificateValidationError | Property | Type | Required | Description | | - | - | - | - | | `code` | `string` | No | | | `message` | `string` | No | | | `remediation` | `string` | No | | | `timestamp` | `string` | No | | ## CheckStatus | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | | | `output` | `string` | No | | | `status` | `string` | No | | | `updated_at` | `string` | No | | ## createAcmeCertificateRequest | Property | Type | Required | Description | | - | - | - | - | | `hostname` | `string` | No | | ## CreateAppDeployTokenRequest | Property | Type | Required | Description | | - | - | - | - | | `expiry` | `string` | No | | ## CreateAppDeployTokenResponse | Property | Type | Required | Description | | - | - | - | - | | `token` | `string` | No | | ## CreateAppRequest | Property | Type | Required | Description | | - | - | - | - | | `enable_subdomains` | `boolean` | No | | | `idempotency_key` | `string` | No | When set, makes a retry of this exact request safe: a second create with the same key and the same name (or no name) returns the app the first request created, instead of erroring on a false name conflict or creating a duplicate. A second create with the same key but a *different* name is rejected outright; it won't silently hand back the first app. | | `name` | `string` | No | | | `network` | `string` | No | | | `org_slug` | `string` | No | | ## CreateAppResponse | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `integer` | No | | | `id` | `string` | No | | ## createCustomCertificateRequest | Property | Type | Required | Description | | - | - | - | - | | `fullchain` | `string` | No | | | `hostname` | `string` | No | | | `private_key` | `string` | No | | ## CreateLeaseRequest | Property | Type | Required | Description | | - | - | - | - | | `description` | `string` | No | | | `ttl` | `integer` | No | seconds lease will be valid | ## CreateMachineRequest | Property | Type | Required | Description | | - | - | - | - | | `config` | `fly.MachineConfig` | No | An object defining the Machine configuration | | `lease_ttl` | `integer` | No | | | `min_secrets_version` | `integer` | No | The minimum version of the app's secrets the Machine must run with. Set it to the `version` returned by the endpoints that set or delete app secrets, so that the Machine is guaranteed to see those changes. This field belongs at the top level of the request body, not in `config`. If omitted, the app's current secrets version is looked up for you. | | `name` | `string` | No | Unique name for this Machine. If omitted, one is generated for you | | `region` | `string` | No | The target region. Omitting this param launches in the same region as your WireGuard peer connection (somewhere near you). | | `skip_launch` | `boolean` | No | | | `skip_secrets` | `boolean` | No | | | `skip_service_registration` | `boolean` | No | | ## CreateOIDCTokenRequest Optional parameters | Property | Type | Required | Description | | - | - | - | - | | `aud` | `string` | No | | | `aws_principal_tags` | `boolean` | No | | ## createPostgresAttachmentRequest | Property | Type | Required | Description | | - | - | - | - | | `app_name` | `string` | Yes | Name of the Fly app to attach. | ## createPostgresAttachmentResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresAttachment` | No | | ## createPostgresBackupRequest | Property | Type | Required | Description | | - | - | - | - | | `type` | `string` | Yes | Backup type. Enum: full, incr, diff | ## createPostgresClusterRequest | Property | Type | Required | Description | | - | - | - | - | | `disk_size_gb` | `integer` | No | Disk size in gigabytes. | | `name` | `string` | No | Name for the cluster. A name is generated when omitted. | | `org_slug` | `string` | Yes | Slug of the organization that will own the cluster, or "personal" for the caller's personal organization. | | `pg_major_version` | `string` | No | Postgres major version. Enum: 16, 17 | | `plan` | `string` | Yes | Plan slug selecting CPU, memory, and disk sizing. Matched case-insensitively. Enum: basic, starter, launch, scale, Performance | | `pool_mode` | `string` | No | Connection pooler mode. Enum: session, transaction | | `postgis_enabled` | `boolean` | No | Enable PostGIS support, required to later enable PostGIS extensions. | | `region` | `string` | Yes | Fly region code where the cluster's primary runs. | ## createPostgresDatabaseRequest | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | Yes | Name of the database to create. Must start and end with an alphanumeric character and may contain hyphens and underscores, up to 63 characters. | ## createPostgresUserRequest | Property | Type | Required | Description | | - | - | - | - | | `role` | `string` | Yes | Role to grant the user. Enum: schema\_admin, writer, reader | | `username` | `string` | Yes | Name for the new user. Must start and end with a lowercase alphanumeric character and may contain hyphens and underscores, up to 63 characters. | ## createPostgresUserResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresUser` | No | | ## CreateVolumeRequest | Property | Type | Required | Description | | - | - | - | - | | `auto_backup_enabled` | `boolean` | No | enable scheduled automatic snapshots. Defaults to `true` | | `compute` | `fly.MachineGuest` | No | | | `compute_image` | `string` | No | | | `encrypted` | `boolean` | No | | | `fstype` | `string` | No | | | `name` | `string` | No | | | `region` | `string` | No | | | `require_unique_zone` | `boolean` | No | | | `size_gb` | `integer` | No | | | `snapshot_id` | `string` | No | restore from snapshot | | `snapshot_retention` | `integer` | No | | | `source_volume_id` | `string` | No | fork from remote volume | | `unique_zone_app_wide` | `boolean` | No | | ## CurrentTokenResponse | Property | Type | Required | Description | | - | - | - | - | | `tokens` | `main.tokenInfo[]` | No | | ## DecryptSecretkeyRequest | Property | Type | Required | Description | | - | - | - | - | | `associated_data` | `integer[]` | No | | | `ciphertext` | `integer[]` | No | | ## DecryptSecretkeyResponse | Property | Type | Required | Description | | - | - | - | - | | `plaintext` | `integer[]` | No | | ## DeleteAppSecretResponse | Property | Type | Required | Description | | - | - | - | - | | `Version` | `integer` | No | DEPRECATED | | `version` | `integer` | No | The app's secrets version after this change. Pass it as `min_secrets_version` when creating or updating Machines so that they are guaranteed to see the change. | ## DeleteSecretkeyResponse | Property | Type | Required | Description | | - | - | - | - | | `Version` | `integer` | No | DEPRECATED | | `version` | `integer` | No | | ## destroyCustomCertificateResponse | Property | Type | Required | Description | | - | - | - | - | | `acme_requested` | `boolean` | No | | | `certificates` | `CertificateEntry[]` | No | | | `configured` | `boolean` | No | | | `dns_provider` | `string` | No | | | `dns_requirements` | `DNSRequirements` | No | | | `hostname` | `string` | No | | | `rate_limited_until` | `string` | No | | | `status` | `string` | No | | | `validation` | `CertificateValidation` | No | | | `validation_errors` | `CertificateValidationError[]` | No | | | `warning` | `string` | No | | ## DNSRecords | Property | Type | Required | Description | | - | - | - | - | | `a` | `string[]` | No | | | `aaaa` | `string[]` | No | | | `acme_challenge_cname` | `string` | No | | | `cname` | `string[]` | No | | | `ownership_txt` | `string` | No | | | `resolved_addresses` | `string[]` | No | | | `soa` | `string` | No | | ## DNSRequirements | Property | Type | Required | Description | | - | - | - | - | | `a` | `string[]` | No | | | `aaaa` | `string[]` | No | | | `acme_challenge` | `AcmeChallenge` | No | | | `cname` | `string` | No | | | `ownership` | `OwnershipVerification` | No | | ## enablePostgresExtensionRequest | Property | Type | Required | Description | | - | - | - | - | | `create_schema` | `boolean` | No | Create the schema first if it does not exist. | | `name` | `string` | Yes | Extension to enable. | | `schema` | `string` | No | Schema to install the extension into. Defaults to the database's default schema. | ## EncryptSecretkeyRequest | Property | Type | Required | Description | | - | - | - | - | | `associated_data` | `integer[]` | No | | | `plaintext` | `integer[]` | No | | ## EncryptSecretkeyResponse | Property | Type | Required | Description | | - | - | - | - | | `ciphertext` | `integer[]` | No | | ## ErrorResponse | Property | Type | Required | Description | | - | - | - | - | | `details` | `object` | No | Deprecated | | `error` | `string` | No | | | `status` | `main.statusCode` | No | | ## ExtendVolumeRequest | Property | Type | Required | Description | | - | - | - | - | | `size_gb` | `integer` | No | | ## ExtendVolumeResponse | Property | Type | Required | Description | | - | - | - | - | | `needs_restart` | `boolean` | No | | | `volume` | `Volume` | No | | ## fly.ContainerConfig | Property | Type | Required | Description | | - | - | - | - | | `cmd` | `string[]` | No | CmdOverride is used to override the default command of the image. | | `depends_on` | `fly.ContainerDependency[]` | No | DependsOn can be used to define dependencies between containers. The container will only be started after all of its dependent conditions have been satisfied. | | `entrypoint` | `string[]` | No | EntrypointOverride is used to override the default entrypoint of the image. | | `env` | `map` | No | ExtraEnv is used to add additional environment variables to the container. | | `env_from` | `fly.EnvFrom[]` | No | EnvFrom can be provided to set environment variables from machine fields. | | `exec` | `string[]` | No | Image Config overrides - these fields are used to override the image configuration. If not provided, the image configuration will be used. ExecOverride is used to override the default command of the image. | | `files` | `fly.File[]` | No | Files are files that will be written to the container file system. | | `healthchecks` | `fly.ContainerHealthcheck[]` | No | Healthchecks determine the health of your containers. Healthchecks can use HTTP, TCP or an Exec command. | | `image` | `string` | No | Image is the docker image to run. | | `name` | `string` | No | Name is used to identify the container in the machine. | | `restart` | `fly.MachineRestart` | No | Restart is used to define the restart policy for the container. | | `secrets` | `fly.MachineSecret[]` | No | Secrets can be provided at the process level to explicitly indicate which secrets should be used for the process. If not provided, the secrets provided at the machine level will be used. | | `stop` | `fly.StopConfig` | No | Stop is used to define the signal and timeout for stopping the container. | | `user` | `string` | No | UserOverride is used to override the default user of the image. | ## fly.ContainerDependency | Property | Type | Required | Description | | - | - | - | - | | `condition` | `fly.ContainerDependencyCondition` | No | | | `name` | `string` | No | | ## fly.ContainerDependencyCondition Type: `string` Enum: exited\_successfully, healthy, started ## fly.ContainerHealthcheck | Property | Type | Required | Description | | - | - | - | - | | `exec` | `fly.ExecHealthcheck` | No | | | `failure_threshold` | `integer` | No | The number of times the check must fail before considering the container unhealthy. | | `grace_period` | `integer` | No | The time in seconds to wait after a container starts before checking its health. | | `http` | `fly.HTTPHealthcheck` | No | | | `interval` | `integer` | No | The time in seconds between executing the defined check. | | `kind` | `fly.ContainerHealthcheckKind` | No | Kind of healthcheck (readiness, liveness) | | `name` | `string` | No | The name of the check. Must be unique within the container. | | `success_threshold` | `integer` | No | The number of times the check must succeeed before considering the container healthy. | | `tcp` | `fly.TCPHealthcheck` | No | | | `timeout` | `integer` | No | The time in seconds to wait for the check to complete. | | `unhealthy` | `fly.UnhealthyPolicy` | No | Unhealthy policy that determines what action to take if a container is deemed unhealthy | ## fly.ContainerHealthcheckKind Type: `string` Enum: readiness, liveness ## fly.ContainerHealthcheckScheme Type: `string` Enum: http, https ## fly.DNSConfig | Property | Type | Required | Description | | - | - | - | - | | `dns_forward_rules` | `fly.dnsForwardRule[]` | No | | | `hostname` | `string` | No | | | `hostname_fqdn` | `string` | No | | | `nameservers` | `string[]` | No | | | `options` | `fly.dnsOption[]` | No | | | `searches` | `string[]` | No | | | `skip_registration` | `boolean` | No | | ## fly.dnsForwardRule | Property | Type | Required | Description | | - | - | - | - | | `addr` | `string` | No | | | `basename` | `string` | No | | ## fly.dnsOption | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | | | `value` | `string` | No | | ## fly.EnvFrom EnvVar defines an environment variable to be populated from a machine field, env\_var | Property | Type | Required | Description | | - | - | - | - | | `env_var` | `string` | No | EnvVar is required and is the name of the environment variable that will be set from the secret. It must be a valid environment variable name. | | `field_ref` | `string` | No | FieldRef selects a field of the Machine: supports id, version, app\_name, private\_ip, region, image. Enum: id, version, app\_name, private\_ip, region, image | ## fly.ExecHealthcheck | Property | Type | Required | Description | | - | - | - | - | | `command` | `string[]` | No | The command to run to check the health of the container (e.g. \["cat", "/tmp/healthy"]) | ## fly.File A file that will be written to the Machine. One of RawValue or SecretName must be set. | Property | Type | Required | Description | | - | - | - | - | | `guest_path` | `string` | No | GuestPath is the path on the machine where the file will be written and must be an absolute path. For example: /full/path/to/file.json | | `image_config` | `string` | No | The name of an image to use the OCI image config as the file contents. | | `mode` | `integer` | No | Mode bits used to set permissions on this file as accepted by chmod(2). | | `raw_value` | `string` | No | The base64 encoded string of the file contents. | | `secret_name` | `string` | No | The name of the secret that contains the base64 encoded file contents. | ## fly.HTTPHealthcheck | Property | Type | Required | Description | | - | - | - | - | | `headers` | `fly.MachineHTTPHeader[]` | No | Additional headers to send with the request | | `method` | `string` | No | The HTTP method to use to when making the request | | `path` | `string` | No | The path to send the request to | | `port` | `integer` | No | The port to connect to, often the same as internal\_port | | `scheme` | `fly.ContainerHealthcheckScheme` | No | Whether to use http or https | | `tls_server_name` | `string` | No | If the protocol is https, the hostname to use for TLS certificate validation | | `tls_skip_verify` | `boolean` | No | If the protocol is https, whether or not to verify the TLS certificate | ## fly.HTTPOptions | Property | Type | Required | Description | | - | - | - | - | | `compress` | `boolean` | No | | | `h2_backend` | `boolean` | No | | | `headers_read_timeout` | `integer` | No | | | `idle_timeout` | `integer` | No | | | `replay_cache` | `fly.ReplayCache[]` | No | | | `response` | `fly.HTTPResponseOptions` | No | | ## fly.HTTPResponseOptions | Property | Type | Required | Description | | - | - | - | - | | `headers` | `map` | No | | | `pristine` | `boolean` | No | | ## fly.MachineCacheDrive | Property | Type | Required | Description | | - | - | - | - | | `size_mb` | `integer` | No | | ## fly.MachineCheck | Property | Type | Required | Description | | - | - | - | - | | `grace_period` | `string` | No | The time to wait after a VM starts before checking its health | | `headers` | `fly.MachineHTTPHeader[]` | No | | | `interval` | `string` | No | The time between connectivity checks | | `kind` | `string` | No | Kind of the check (informational, readiness) Enum: informational, readiness | | `method` | `string` | No | For http checks, the HTTP method to use to when making the request | | `path` | `string` | No | For http checks, the path to send the request to | | `port` | `integer` | No | The port to connect to, often the same as internal\_port | | `protocol` | `string` | No | For http checks, whether to use http or https | | `timeout` | `string` | No | The maximum time a connection can take before being reported as failing its health check | | `tls_server_name` | `string` | No | If the protocol is https, the hostname to use for TLS certificate validation | | `tls_skip_verify` | `boolean` | No | For http checks with https protocol, whether or not to verify the TLS certificate | | `type` | `string` | No | tcp or http | ## fly.MachineConfig | Property | Type | Required | Description | | - | - | - | - | | `auto_destroy` | `boolean` | No | Optional boolean telling the Machine to destroy itself once it’s complete (default false) | | `cache_drive` | `fly.MachineCacheDrive` | No | A cache drive attached to the Machine | | `checks` | `map` | No | An optional object that defines one or more named top-level checks. The key for each check is the check name. | | `containers` | `fly.ContainerConfig[]` | No | Containers are a list of containers that will run in the machine, each from its own image. | | `disable_machine_autostart` | `boolean` | No | Deprecated: use Service.Autostart instead | | `dns` | `fly.DNSConfig` | No | DNS settings for the Machine | | `env` | `map` | No | An object filled with key/value pairs to be set as environment variables | | `files` | `fly.File[]` | No | Files to write into the Machine, from either a raw value or an app secret | | `guest` | `fly.MachineGuest` | No | The resources (CPUs, memory, etc.) allocated to the Machine | | `image` | `string` | No | The docker image to run | | `init` | `fly.MachineInit` | No | Arguments for init, Fly.io's footprint inside the Machine, which controls how your own code is run | | `metadata` | `map` | No | Key/value pairs of Machine metadata. Fly.io uses metadata internally for routing, process groups, and clusters | | `metrics` | `fly.MachineMetrics` | No | An optional metrics endpoint for Prometheus on Fly.io to scrape | | `mounts` | `fly.MachineMount[]` | No | Persistent volumes to mount in the Machine. A Machine can mount at most one volume, which must be on the same host. The mounted volume cannot be changed by updating the Machine | | `processes` | `fly.MachineProcess[]` | No | Multiple processes to run within this one Machine, all from the Machine's image. Unrelated to the process groups in fly.toml, which run on separate Machines. To run processes from different images, use containers instead | | `restart` | `fly.MachineRestart` | No | Whether and how the Machine is restarted after its main process exits | | `rootfs` | `fly.MachineRootfs` | No | The size of the Machine's root filesystem and whether it persists | | `schedule` | `string` | No | Run the Machine at an interval: one of hourly, daily, weekly or monthly. The interval starts when the Machine is created | | `services` | `fly.MachineService[]` | No | Network services that Fly Proxy routes traffic to | | `size` | `string` | No | Deprecated: use Guest instead | | `spot` | `fly.MachineSpot` | No | Spot pricing settings for the Machine | | `standbys` | `string[]` | No | Standbys enable a machine to be a standby for another. In the event of a hardware failure, the standby machine will be started. | | `statics` | `fly.Static[]` | No | Static files to serve under a URL prefix. Fly Proxy routes matching requests to a file server inside the Machine, bypassing your application's own process; the Machine must be running. The files are read either from a path inside the Machine or from a Tigris bucket that belongs to the app; no bucket is created for you | | `stop_config` | `fly.StopConfig` | No | How the Machine is shut down gracefully: the signal sent to it and how long to wait before a hard shutdown | ## fly.MachineGuest | Property | Type | Required | Description | | - | - | - | - | | `cpu_kind` | `string` | No | | | `cpus` | `integer` | No | | | `gpu_kind` | `string` | No | | | `gpus` | `integer` | No | | | `host_dedication_id` | `string` | No | | | `kernel_args` | `string[]` | No | | | `max_memory_mb` | `integer` | No | | | `memory_mb` | `integer` | No | | | `persist_rootfs` | `string` | No | Deprecated: use MachineConfig.Rootfs instead Enum: never, always, restart | | `required_host_features` | `string[]` | No | | ## fly.MachineHTTPHeader For http checks, an array of objects with string field Name and array of strings field Values. The key/value pairs specify header and header values that will get passed with the check call. | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | The header name | | `values` | `string[]` | No | The header value | ## fly.MachineInit | Property | Type | Required | Description | | - | - | - | - | | `cmd` | `string[]` | No | | | `entrypoint` | `string[]` | No | | | `exec` | `string[]` | No | | | `kernel_args` | `string[]` | No | | | `swap_size_mb` | `integer` | No | | | `tty` | `boolean` | No | | ## fly.MachineMetrics | Property | Type | Required | Description | | - | - | - | - | | `https` | `boolean` | No | | | `path` | `string` | No | | | `port` | `integer` | No | | ## fly.MachineMount | Property | Type | Required | Description | | - | - | - | - | | `add_size_gb` | `integer` | No | | | `add_size_percent` | `integer` | No | AddSizePercent grows by a percentage of the current size, rounded up to GiB. It is mutually exclusive with AddSizeGb. | | `encrypted` | `boolean` | No | | | `extend_threshold_percent` | `integer` | No | | | `min_add_size_gb` | `integer` | No | MinAddSizeGb is the minimum proportional growth in GiB (default 1). | | `name` | `string` | No | | | `path` | `string` | No | | | `size_gb` | `integer` | No | | | `size_gb_limit` | `integer` | No | | | `volume` | `string` | No | | ## fly.MachinePort | Property | Type | Required | Description | | - | - | - | - | | `end_port` | `integer` | No | | | `force_https` | `boolean` | No | | | `handlers` | `string[]` | No | | | `http_options` | `fly.HTTPOptions` | No | | | `port` | `integer` | No | | | `proxy_proto_options` | `fly.ProxyProtoOptions` | No | | | `start_port` | `integer` | No | | | `tls_options` | `fly.TLSOptions` | No | | ## fly.MachineProcess | Property | Type | Required | Description | | - | - | - | - | | `cmd` | `string[]` | No | | | `entrypoint` | `string[]` | No | | | `env` | `map` | No | | | `env_from` | `fly.EnvFrom[]` | No | EnvFrom can be provided to set environment variables from machine fields. | | `exec` | `string[]` | No | | | `ignore_app_secrets` | `boolean` | No | IgnoreAppSecrets can be set to true to ignore the secrets for the App the Machine belongs to and only use the secrets provided at the process level. The default/legacy behavior is to use the secrets provided at the App level. | | `secrets` | `fly.MachineSecret[]` | No | Secrets can be provided at the process level to explicitly indicate which secrets should be used for the process. If not provided, the secrets provided at the machine level will be used. | | `user` | `string` | No | | ## fly.MachineRestart The Machine restart policy defines whether and how flyd restarts a Machine after its main process exits. See [https://fly.io/docs/machines/guides-examples/machine-restart-policy/](https://fly.io/docs/machines/guides-examples/machine-restart-policy/). | Property | Type | Required | Description | | - | - | - | - | | `max_retries` | `integer` | No | When policy is on-failure, the maximum number of times to attempt to restart the Machine before letting it stop. | | `policy` | `string` | No | \* no - Never try to restart a Machine automatically when its main process exits, whether that’s on purpose or on a crash. \* always - Always restart a Machine automatically and never let it enter a stopped state, even when the main process exits cleanly. \* on-failure - Try up to MaxRetries times to automatically restart the Machine if it exits with a non-zero exit code. Default when no explicit policy is set, and for Machines with schedules. Enum: no, always, on-failure | ## fly.MachineRootfs | Property | Type | Required | Description | | - | - | - | - | | `persist` | `string` | No | Enum: never, always, restart | | `size_gb` | `integer` | No | | ## fly.MachineSecret A Secret needing to be set in the environment of the Machine. env\_var is required | Property | Type | Required | Description | | - | - | - | - | | `env_var` | `string` | No | EnvVar is required and is the name of the environment variable that will be set from the secret. It must be a valid environment variable name. | | `name` | `string` | No | Name is optional and when provided is used to reference a secret name where the EnvVar is different from what was set as the secret name. | ## fly.MachineService | Property | Type | Required | Description | | - | - | - | - | | `autostart` | `boolean` | No | | | `autostop` | `string` | No | Accepts a string (new format) or a boolean (old format). For backward compatibility with older clients, the API continues to use booleans for "off" and "stop" in responses. \* "off" or false - Do not autostop the Machine. \* "stop" or true - Automatically stop the Machine. \* "suspend" - Automatically suspend the Machine, falling back to a full stop if this is not possible. Enum: off, stop, suspend | | `checks` | `fly.MachineServiceCheck[]` | No | An optional list of service checks | | `concurrency` | `fly.MachineServiceConcurrency` | No | | | `force_instance_description` | `string` | No | | | `force_instance_key` | `string` | No | | | `internal_port` | `integer` | No | | | `min_machines_running` | `integer` | No | | | `ports` | `fly.MachinePort[]` | No | | | `protocol` | `string` | No | | ## fly.MachineServiceCheck | Property | Type | Required | Description | | - | - | - | - | | `grace_period` | `string` | No | The time to wait after a VM starts before checking its health | | `headers` | `fly.MachineHTTPHeader[]` | No | | | `interval` | `string` | No | The time between connectivity checks | | `method` | `string` | No | For http checks, the HTTP method to use to when making the request | | `path` | `string` | No | For http checks, the path to send the request to | | `port` | `integer` | No | The port to connect to, often the same as internal\_port | | `protocol` | `string` | No | For http checks, whether to use http or https | | `timeout` | `string` | No | The maximum time a connection can take before being reported as failing its health check | | `tls_server_name` | `string` | No | If the protocol is https, the hostname to use for TLS certificate validation | | `tls_skip_verify` | `boolean` | No | For http checks with https protocol, whether or not to verify the TLS certificate | | `type` | `string` | No | tcp or http | ## fly.MachineServiceConcurrency | Property | Type | Required | Description | | - | - | - | - | | `hard_limit` | `integer` | No | | | `soft_limit` | `integer` | No | | | `type` | `string` | No | | ## fly.MachineSpot | Property | Type | Required | Description | | - | - | - | - | | `max_price_fraction` | `number` | No | MaxPriceFraction is the maximum fraction of the full Machine price you will pay for this Machine. Range: (0, 1.0] | ## fly.ProxyProtoOptions | Property | Type | Required | Description | | - | - | - | - | | `fly_src` | `boolean` | No | | | `version` | `string` | No | | ## fly.ReplayCache | Property | Type | Required | Description | | - | - | - | - | | `allow_bypass` | `boolean` | No | | | `name` | `string` | No | Name of the cookie or header to key the cache on | | `path_prefix` | `string` | No | | | `ttl_seconds` | `integer` | No | | | `type` | `string` | No | Currently either "cookie" or "header" Enum: cookie, header | ## fly.Static | Property | Type | Required | Description | | - | - | - | - | | `guest_path` | `string` | Yes | The path inside the Machine, or inside tigris\_bucket when set, where the files to serve are located | | `index_document` | `string` | No | The document served for requests to the URL prefix or any of its subfolders. Only works with tigris\_bucket | | `tigris_bucket` | `string` | No | An existing Tigris bucket belonging to the app to read the files from, instead of the Machine's filesystem. The files are still served through the Machine | | `url_prefix` | `string` | Yes | The URL prefix under which the files are served | ## fly.StopConfig | Property | Type | Required | Description | | - | - | - | - | | `signal` | `string` | No | Enum: SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGUSR1, SIGUSR2, SIGTERM | | `timeout` | `string` | No | | ## fly.TCPHealthcheck | Property | Type | Required | Description | | - | - | - | - | | `port` | `integer` | No | The port to connect to, often the same as internal\_port | ## fly.TLSOptions | Property | Type | Required | Description | | - | - | - | - | | `alpn` | `string[]` | No | | | `default_self_signed` | `boolean` | No | | | `versions` | `string[]` | No | | ## fly.UnhealthyPolicy Type: `string` Enum: stop ## flydv1.ExecResponse | Property | Type | Required | Description | | - | - | - | - | | `exit_code` | `integer` | No | | | `exit_signal` | `integer` | No | | | `stderr` | `string` | No | | | `stdout` | `string` | No | | ## flyio.Access | Property | Type | Required | Description | | - | - | - | - | | `action` | `resset.Action` | No | | | `app_feature` | `string` | No | | | `appid` | `integer` | No | | | `cluster` | `string` | No | | | `command` | `string[]` | No | | | `feature` | `string` | No | | | `machine` | `string` | No | | | `machine_feature` | `string` | No | | | `mutation` | `string` | No | | | `orgid` | `integer` | No | | | `sourceApp` | `string` | No | | | `sourceMachine` | `string` | No | | | `sourceOrganization` | `string` | No | | | `storage_object` | `string` | No | | | `volume` | `string` | No | | ## forkPostgresClusterRequest | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | Name for the forked cluster. Defaults to the source name with a -fork suffix. | | `plan` | `string` | No | Plan slug selecting CPU and memory sizing for the fork. Defaults to the source cluster's plan. Matched case-insensitively. Enum: basic, starter, launch, scale, Performance | ## getPostgresUserCredentialsResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresUserCredentials` | No | | ## ImageRef | Property | Type | Required | Description | | - | - | - | - | | `digest` | `string` | No | | | `labels` | `map` | No | | | `registry` | `string` | No | | | `repository` | `string` | No | | | `tag` | `string` | No | | ## IPAssignment | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `string` | No | | | `egress` | `boolean` | No | | | `ip` | `string` | No | | | `network` | `IPAssignmentNetwork` | No | | | `region` | `string` | No | | | `service_name` | `string` | No | | | `shared` | `boolean` | No | | ## IPAssignmentNetwork The 6PN network a Flycast (private\_v6) address belongs to. Null for all other IP types. | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | Network name; empty for the organization's default network. | | `org_slug` | `string` | No | | ## IPAssignmentType Type of IP address to allocate. "egress-pair" allocates both v4 and v6 egress IP addresses (recommended when using egress IPs). Type: `string` Enum: v4, v6, shared\_v4, private\_v6, egress\_v4, egress\_v6, egress\_pair ## IPPair | Property | Type | Required | Description | | - | - | - | - | | `v4` | `string` | No | | | `v6` | `string` | No | | ## IssuedCertificate | Property | Type | Required | Description | | - | - | - | - | | `certificate_authority` | `string` | No | | | `expires_at` | `string` | No | | | `type` | `string` | No | Enum: rsa, ecdsa | ## Lease | Property | Type | Required | Description | | - | - | - | - | | `description` | `string` | No | Description or reason for the Lease. | | `expires_at` | `integer` | No | ExpiresAt is the unix timestamp in UTC to denote when the Lease will no longer be valid. | | `nonce` | `string` | No | Nonce is the unique ID autogenerated and associated with the Lease. | | `owner` | `string` | No | Owner is the user identifier which acquired the Lease. | | `version` | `string` | No | Machine version | ## ListAppsResponse | Property | Type | Required | Description | | - | - | - | - | | `apps` | `App[]` | No | | | `next_cursor` | `string` | No | Pagination cursor for the next page. Absent when no more apps remain. Cursors expire 30 minutes after the first page was requested. | | `total_apps` | `integer` | No | The number of apps matching the request, across all pages. When paginating, it is counted once when the first page is requested and not updated afterwards, so it may differ slightly from the number of apps returned: it excludes apps created later and includes apps deleted while paginating. Apps created in the seconds before the first page may also be counted but missing from the pages. | ## listCertificatesResponse | Property | Type | Required | Description | | - | - | - | - | | `certificates` | `CertificateSummary[]` | No | | | `next_cursor` | `string` | No | | | `total_count` | `integer` | No | | ## ListenSocket | Property | Type | Required | Description | | - | - | - | - | | `address` | `string` | No | | | `proto` | `string` | No | | ## listIPAssignmentsResponse | Property | Type | Required | Description | | - | - | - | - | | `ips` | `IPAssignment[]` | No | | ## listPostgresActiveQueriesResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresActiveQuery[]` | No | | ## listPostgresBackupsResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresBackup[]` | No | | ## listPostgresClustersResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresClusterSummary[]` | No | | ## listPostgresDatabasesResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresDatabase[]` | No | | ## listPostgresExtensionsResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresExtension[]` | No | | ## listPostgresPlansResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresPlan[]` | No | | ## listPostgresSlowQueriesResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresSlowQuery[]` | No | | ## listPostgresUsersResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresUser[]` | No | | ## macaroon.CaveatSet | Property | Type | Required | Description | | - | - | - | - | | `caveats` | `object[]` | No | | ## macaroon.Nonce | Property | Type | Required | Description | | - | - | - | - | | `kid` | `integer[]` | No | | | `proof` | `boolean` | No | | | `rnd` | `integer[]` | No | | ## Machine | Property | Type | Required | Description | | - | - | - | - | | `checks` | `CheckStatus[]` | No | | | `config` | `fly.MachineConfig` | No | | | `cordoned` | `boolean` | No | | | `created_at` | `string` | No | | | `events` | `MachineEvent[]` | No | | | `host_features` | `string[]` | No | | | `host_status` | `string` | No | Enum: ok, unknown, unreachable | | `id` | `string` | No | | | `image_ref` | `ImageRef` | No | | | `incomplete_config` | `fly.MachineConfig` | No | | | `instance_id` | `string` | No | InstanceID is the same value as `version`. Deprecated: use `version`. | | `lease` | `StrippedLease` | No | | | `name` | `string` | No | | | `nonce` | `string` | No | Nonce is only every returned on machine creation if a lease\_duration was provided. | | `private_ip` | `string` | No | PrivateIP is the internal 6PN address of the machine. | | `region` | `string` | No | | | `state` | `string` | No | | | `updated_at` | `string` | No | | | `version` | `string` | No | Version is unique for each version of the machine. Pass it as `current_version` when updating the Machine to reject the update if the Machine has changed since this version. | ## MachineEvent | Property | Type | Required | Description | | - | - | - | - | | `id` | `string` | No | | | `request` | `object` | No | | | `source` | `string` | No | | | `status` | `string` | No | | | `timestamp` | `integer` | No | | | `type` | `string` | No | | ## MachineExecRequest | Property | Type | Required | Description | | - | - | - | - | | `cmd` | `string` | No | Deprecated: use Command instead | | `command` | `string[]` | No | | | `container` | `string` | No | | | `machine` | `boolean` | No | Machine runs the command in the machine's own namespace instead of in a container. It is mutually exclusive with Container. | | `stdin` | `string` | No | | | `timeout` | `integer` | No | | ## MachineOverviewConfig | Property | Type | Required | Description | | - | - | - | - | | `guest` | `fly.MachineGuest` | No | | | `image` | `string` | No | | | `metadata` | `map` | No | | ## MachineVersion | Property | Type | Required | Description | | - | - | - | - | | `user_config` | `fly.MachineConfig` | No | | | `version` | `string` | No | | ## main.getPlacementsRequest | Property | Type | Required | Description | | - | - | - | - | | `compute` | `fly.MachineGuest` | No | Resource requirements for the Machine to simulate. Defaults to a performance-1x machine | | `count` | `integer` | No | Number of machines to simulate placement. Defaults to 0, which returns the org-specific limit for each region. | | `org_slug` | `string` | Yes | | | `region` | `string` | No | Region expression for placement as a comma-delimited set of regions or aliases. Defaults to "\[region],any", to prefer the API endpoint's local region with any other region as fallback. | | `volume_name` | `string` | No | | | `volume_size_bytes` | `integer` | No | | | `weights` | `placement.Weights` | No | Optional weights to override default placement preferences. | ## main.getPlacementsResponse | Property | Type | Required | Description | | - | - | - | - | | `regions` | `placement.RegionPlacement[]` | No | | ## main.memoryResponse | Property | Type | Required | Description | | - | - | - | - | | `available_mb` | `integer` | No | | | `limit_mb` | `integer` | No | | ## main.reclaimMemoryRequest | Property | Type | Required | Description | | - | - | - | - | | `amount_mb` | `integer` | No | | ## main.reclaimMemoryResponse | Property | Type | Required | Description | | - | - | - | - | | `actual_mb` | `integer` | No | | ## main.regionResponse | Property | Type | Required | Description | | - | - | - | - | | `nearest` | `string` | No | | | `regions` | `main.regionRow[]` | No | | ## main.regionRow | Property | Type | Required | Description | | - | - | - | - | | `code` | `string` | No | | | `deprecated` | `boolean` | No | | | `gateway_available` | `boolean` | No | | | `geo_region` | `string` | No | | | `latitude` | `number` | No | | | `longitude` | `number` | No | | | `mpg_available` | `boolean` | No | | | `name` | `string` | No | | | `requires_paid_plan` | `boolean` | No | | ## main.setMemoryLimitRequest | Property | Type | Required | Description | | - | - | - | - | | `limit_mb` | `integer` | No | | ## main.statusCode Type: `string` Enum: unknown, insufficient\_capacity, volume\_placement\_capacity, name\_taken ## main.TokenAccess | Property | Type | Required | Description | | - | - | - | - | | `action` | `resset.Action` | No | Action is the action being taken on the specified resource. This is the combination of individual action characters (e.g "rw") - r: read - w: write - c: create - d: delete - C: control | | `app_feature` | `string` | No | AppFeature is a named set of functionality associated with the app. If this is specified, the AppName field must be set. - images: images in the fly.io registry | | `app_name` | `string` | No | AppName is the name of the app being accessed. | | `command` | `string[]` | No | Command is the command being executed on a machine. If this is specified, the Machine must be set. | | `machine_feature` | `string` | No | MachineFeature is a named set of functionality associated with the machine. If this is specified, the Machine field must be set. - metadata: machine metadata service - oidc: OIDC tokens - kmstoken: Petsem tokens for KMS access | | `machine_id` | `string` | No | MachineID is the ID of the machine being accessed (e.g. 7811701f564258). | | `mutation` | `string` | No | Mutation is the GraphQL mutation being performed. | | `org_feature` | `string` | No | OrgFeature is a named set of functionality associated with the organization. If this is specified, the OrgSlug field must be set. - wg: WireGuard peers - builder: remote builders - addon: addons - membership: organization membership - billing: billing - litefs-cloud: LiteFS Cloud - authentication: authentication settings | | `org_slug` | `string` | No | OrgSlug is the slug of the organization being accessed. | | `source_machine` | `string` | No | SourceMachine is the machine ID of the actor attempting access. | | `storage_object` | `string` | No | StorageObject is the storage object being accessed. If this is specified, the OrgSlug must be set. | | `volume_id` | `string` | No | VolumeID is the encoded ID of the volume being accessed (e.g. vol\_r1p6pln1k9m9j7zr). | ## main.tokenInfo | Property | Type | Required | Description | | - | - | - | - | | `apps` | `string[]` | No | | | `org_slug` | `string` | No | | | `organization` | `string` | No | | | `restricted_to_machine` | `string` | No | Machine the token is restricted to (FromMachine caveat) | | `source_machine_id` | `string` | No | Machine making the request | | `token_id` | `string` | No | | | `user` | `string` | No | User identifier if token is for a user | ## metadataValueResponse | Property | Type | Required | Description | | - | - | - | - | | `value` | `string` | No | | ## OkResponse | Property | Type | Required | Description | | - | - | - | - | | `ok` | `boolean` | No | | ## OrganizationRef | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | Organization name. | | `slug` | `string` | No | Organization slug. | ## OrgMachine | Property | Type | Required | Description | | - | - | - | - | | `app_name` | `string` | No | | | `config` | `MachineOverviewConfig` | No | | | `created_at` | `string` | No | | | `id` | `string` | No | | | `name` | `string` | No | | | `private_ip` | `string` | No | | | `region` | `string` | No | | | `state` | `string` | No | | | `updated_at` | `string` | No | | | `version` | `string` | No | | ## OrgMachinesResponse | Property | Type | Required | Description | | - | - | - | - | | `error_regions` | `string[]` | No | | | `last_machine_id` | `string` | No | | | `last_updated_at` | `string` | No | | | `machines` | `OrgMachine[]` | No | | | `next_cursor` | `string` | No | | ## OrgVolume | Property | Type | Required | Description | | - | - | - | - | | `app_name` | `string` | No | | | `attached_alloc_id` | `string` | No | | | `attached_machine_id` | `string` | No | | | `auto_backup_enabled` | `boolean` | No | | | `block_size` | `integer` | No | | | `blocks` | `integer` | No | | | `blocks_avail` | `integer` | No | | | `blocks_free` | `integer` | No | | | `bytes_total` | `integer` | No | | | `bytes_used` | `integer` | No | | | `created_at` | `string` | No | | | `encrypted` | `boolean` | No | | | `fstype` | `string` | No | | | `host_features` | `string[]` | No | | | `host_status` | `string` | No | Enum: ok, unknown, unreachable | | `id` | `string` | No | | | `name` | `string` | No | | | `region` | `string` | No | | | `required_host_features` | `string[]` | No | | | `size_gb` | `integer` | No | | | `snapshot_retention` | `integer` | No | | | `state` | `string` | No | | | `type` | `string` | No | Enum: local, cache | | `updated_at` | `string` | No | | | `zone` | `string` | No | | ## OrgVolumesResponse | Property | Type | Required | Description | | - | - | - | - | | `last_updated_at` | `string` | No | | | `last_volume_id` | `string` | No | | | `next_cursor` | `string` | No | | | `volumes` | `OrgVolume[]` | No | | ## OwnershipVerification | Property | Type | Required | Description | | - | - | - | - | | `app_value` | `string` | No | | | `name` | `string` | No | | | `org_value` | `string` | No | | ## placement.RegionPlacement | Property | Type | Required | Description | | - | - | - | - | | `concurrency` | `integer` | No | | | `count` | `integer` | No | | | `region` | `string` | No | | ## placement.Weights Type: `map` ## PostgresActiveQuery | Property | Type | Required | Description | | - | - | - | - | | `application_name` | `string` | No | Application connected to the backend. | | `client` | `string` | No | Client address. | | `database` | `string` | No | Database name. | | `duration_seconds` | `number` | No | Query duration in seconds. | | `pid` | `integer` | No | Process ID of the backend running the query. | | `query` | `string` | No | Full query text. | | `state` | `string` | No | Current backend state. | | `state_duration_seconds` | `number` | No | Time spent in the current state, in seconds. | | `user` | `string` | No | User running the query. | | `wait_event` | `string` | No | Event the backend is waiting for. | | `wait_event_type` | `string` | No | Type of event the backend is waiting for. | ## PostgresAttachedApp | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | Attached Fly app name. | ## PostgresAttachment | Property | Type | Required | Description | | - | - | - | - | | `app_name` | `string` | No | Attached Fly app name. | | `attached_at` | `string` | No | RFC 3339 timestamp when the attachment was created. | | `postgres_cluster_id` | `string` | No | Cluster ID the app is attached to. | ## PostgresBackup | Property | Type | Required | Description | | - | - | - | - | | `finished_at` | `string` | No | RFC 3339 stop timestamp. | | `id` | `string` | No | Backup ID. | | `size_bytes` | `integer` | No | Backup size in bytes. | | `started_at` | `string` | No | RFC 3339 start timestamp. | | `status` | `string` | No | Backup status. | | `type` | `string` | No | Backup type. Enum: full, incr, diff | ## PostgresCluster | Property | Type | Required | Description | | - | - | - | - | | `attached_apps` | `PostgresAttachedApp[]` | No | Apps attached to the cluster. | | `cpu_kind` | `string` | No | CPU class. Enum: shared, performance | | `cpus` | `integer` | No | vCPUs per node. | | `created_at` | `string` | No | RFC 3339 creation timestamp. | | `disk_size_gb` | `integer` | No | Disk size in gigabytes, for one replica. | | `endpoints` | `PostgresClusterEndpoints` | No | Connection endpoints. Populated once the cluster is ready. | | `id` | `string` | No | Cluster ID. | | `memory_mb` | `integer` | No | Memory per node in megabytes. | | `name` | `string` | No | Cluster name. | | `organization` | `OrganizationRef` | No | | | `pg_major_version` | `string` | No | Postgres major version. Enum: 16, 17 | | `plan` | `string` | No | Plan slug. Enum: basic, starter, launch, scale, Performance | | `postgis_enabled` | `boolean` | No | Whether PostGIS support is enabled. | | `region` | `string` | No | Fly region code. | | `replicas` | `integer` | No | Number of replicas. | | `status` | `string` | No | Current lifecycle status. Enum: creating, initializing, ready, deleting, deleted, failed | | `storage_provisioned_bytes` | `integer` | No | Storage provisioned, summed across the cluster's volumes and measured hourly. Null until first measured. | | `storage_used_bytes` | `integer` | No | Storage used, summed across the cluster's volumes and measured hourly. Null until first measured. | ## PostgresClusterEndpoints | Property | Type | Required | Description | | - | - | - | - | | `primary` | `PostgresNodeEndpoints` | No | Endpoints for connecting to the primary node. | ## PostgresClusterSummary | Property | Type | Required | Description | | - | - | - | - | | `attached_apps` | `PostgresAttachedApp[]` | No | Apps attached to the cluster. | | `created_at` | `string` | No | RFC 3339 creation timestamp. | | `deleted_at` | `string` | No | RFC 3339 deletion timestamp; present for deleted clusters. | | `id` | `string` | No | Cluster ID. | | `name` | `string` | No | Cluster name. | | `plan` | `string` | No | Plan slug. Enum: basic, starter, launch, scale, Performance | | `region` | `string` | No | Fly region code. | | `status` | `string` | No | Current lifecycle status. Enum: creating, initializing, ready, deleting, deleted, failed | ## PostgresDatabase | Property | Type | Required | Description | | - | - | - | - | | `name` | `string` | No | Database name. | ## postgresDatabaseResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresDatabase` | No | | ## PostgresEndpoint | Property | Type | Required | Description | | - | - | - | - | | `host` | `string` | No | Hostname to connect to. | | `port` | `integer` | No | TCP port. | ## PostgresErrorResponse | Property | Type | Required | Description | | - | - | - | - | | `error` | `string` | No | Human-readable error message. | | `status` | `string` | No | Optional machine-readable error status. | ## PostgresExtension | Property | Type | Required | Description | | - | - | - | - | | `default_version` | `string` | No | Default version installed when enabled. | | `description` | `string` | No | Human-readable description. | | `installed` | `PostgresInstalledExtension` | No | Installation details, or null when the extension is not installed. | | `name` | `string` | No | Extension name. | | `system` | `boolean` | No | Whether this is a system extension that cannot be disabled. | ## PostgresInstalledExtension | Property | Type | Required | Description | | - | - | - | - | | `schema` | `string` | No | Schema the extension is installed into. | | `version` | `string` | No | Installed version. | ## PostgresNodeEndpoints | Property | Type | Required | Description | | - | - | - | - | | `direct` | `PostgresEndpoint` | No | Unpooled connection to the cluster node. | | `pooler` | `PostgresEndpoint` | No | Pooled connection to the cluster node (through PgBouncer). | ## PostgresPlan | Property | Type | Required | Description | | - | - | - | - | | `cpu_kind` | `string` | No | CPU class. Enum: shared, performance | | `cpus` | `integer` | No | vCPUs per node. | | `memory_mb` | `integer` | No | Memory per node in megabytes. | | `monthly_price_usd` | `integer` | No | Base monthly price in US dollars. | | `plan` | `string` | No | Plan slug. Pass it as `plan` when creating or forking a cluster. Enum: basic, starter, launch, scale, Performance | ## PostgresSlowQuery | Property | Type | Required | Description | | - | - | - | - | | `calls` | `integer` | No | Cumulative number of executions. | | `database` | `string` | No | Database name. | | `max_exec_time_seconds` | `number` | No | Maximum execution time in seconds. | | `mean_exec_time_seconds` | `number` | No | Mean execution time in seconds. | | `query` | `string` | No | Full query pattern. | | `query_id` | `string` | No | Query identifier. | | `total_exec_time_seconds` | `number` | No | Cumulative total execution time in seconds. | | `user` | `string` | No | User running the query. | ## PostgresUser | Property | Type | Required | Description | | - | - | - | - | | `role` | `string` | No | User role. Enum: schema\_admin, writer, reader | | `username` | `string` | No | User name. | ## PostgresUserCredentials | Property | Type | Required | Description | | - | - | - | - | | `password` | `string` | No | User password. | | `username` | `string` | No | User name. | ## ProcessStat | Property | Type | Required | Description | | - | - | - | - | | `command` | `string` | No | | | `cpu` | `integer` | No | | | `directory` | `string` | No | | | `listen_sockets` | `ListenSocket[]` | No | | | `pid` | `integer` | No | | | `rss` | `integer` | No | | | `rtime` | `integer` | No | | | `stime` | `integer` | No | | ## resset.Action Type: `integer` Enum: 1, 2, 4, 8, 16, 31, 0 ## restorePostgresClusterRequest | Property | Type | Required | Description | | - | - | - | - | | `backup_id` | `string` | No | Backup label to restore from, as returned by the backups list. Mutually exclusive with pitr\_time. | | `name` | `string` | No | Name for the restored cluster. Defaults to a generated name derived from the source cluster and backup/point in time when omitted or blank. | | `pitr_time` | `string` | No | Point in time to restore to, as an RFC3339 timestamp with an explicit offset from UTC (e.g. Z or +02:00). Normalized to UTC and must fall within the cluster's PITR recovery window. Mutually exclusive with backup\_id. | ## root.VerifiedToken | Property | Type | Required | Description | | - | - | - | - | | `caveats` | `macaroon.CaveatSet` | No | | | `header` | `string` | No | | | `nonce` | `macaroon.Nonce` | No | | | `permission_token` | `integer[]` | No | | ## rotatePostgresPasswordRequest | Property | Type | Required | Description | | - | - | - | - | | `kill_sessions` | `boolean` | No | Terminate the user's existing sessions after rotating. | ## rotatePostgresPasswordResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresUserCredentials` | No | | ## SecretKey | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `string` | No | | | `name` | `string` | No | | | `public_key` | `integer[]` | No | | | `type` | `string` | No | | | `updated_at` | `string` | No | | ## SecretKeys | Property | Type | Required | Description | | - | - | - | - | | `secret_keys` | `SecretKey[]` | No | | ## SetAppSecretRequest | Property | Type | Required | Description | | - | - | - | - | | `value` | `string` | No | | ## SetAppSecretResponse | Property | Type | Required | Description | | - | - | - | - | | `Version` | `integer` | No | DEPRECATED | | `created_at` | `string` | No | | | `digest` | `string` | No | | | `name` | `string` | No | | | `updated_at` | `string` | No | | | `value` | `string` | No | | | `version` | `integer` | No | The app's secrets version after this change. Pass it as `min_secrets_version` when creating or updating Machines so that they are guaranteed to see the change. | ## SetSecretkeyRequest | Property | Type | Required | Description | | - | - | - | - | | `type` | `string` | No | | | `value` | `integer[]` | No | | ## SetSecretkeyResponse | Property | Type | Required | Description | | - | - | - | - | | `Version` | `integer` | No | DEPRECATED | | `created_at` | `string` | No | | | `name` | `string` | No | | | `public_key` | `integer[]` | No | | | `type` | `string` | No | | | `updated_at` | `string` | No | | | `version` | `integer` | No | | ## showPostgresClusterResponse | Property | Type | Required | Description | | - | - | - | - | | `data` | `PostgresCluster` | No | | ## SignalRequest | Property | Type | Required | Description | | - | - | - | - | | `signal` | `string` | No | Enum: SIGABRT, SIGALRM, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, SIGTRAP, SIGUSR1, SIGUSR2 | ## SignSecretkeyRequest | Property | Type | Required | Description | | - | - | - | - | | `plaintext` | `integer[]` | No | | ## SignSecretkeyResponse | Property | Type | Required | Description | | - | - | - | - | | `signature` | `integer[]` | No | | ## StartMachineResponse | Property | Type | Required | Description | | - | - | - | - | | `migrated` | `boolean` | No | Whether the Machine was moved to another host to be started | | `new_host` | `string` | No | The host the Machine was moved to, if it was migrated | | `previous_state` | `string` | No | The state the Machine was in before it was started | ## StopRequest | Property | Type | Required | Description | | - | - | - | - | | `signal` | `string` | No | Enum: SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGUSR1, SIGUSR2, SIGTERM | | `timeout` | `string` | No | | ## StrippedLease | Property | Type | Required | Description | | - | - | - | - | | `description` | `string` | No | | | `expires_at` | `integer` | No | | | `owner` | `string` | No | | ## UpdateMachineRequest | Property | Type | Required | Description | | - | - | - | - | | `config` | `fly.MachineConfig` | No | An object defining the Machine configuration | | `current_version` | `string` | No | CurrentVersion is an optional optimistic-concurrency guard: the Machine's `version`, as returned by create, get or a previous update. When set and it is no longer the Machine's current version, the update is rejected with 409 Conflict and nothing changes. Omit it to update unconditionally. | | `lease_ttl` | `integer` | No | | | `min_secrets_version` | `integer` | No | The minimum version of the app's secrets the Machine must run with. Set it to the `version` returned by the endpoints that set or delete app secrets, so that the Machine is guaranteed to see those changes. This field belongs at the top level of the request body, not in `config`. If omitted, the app's current secrets version is looked up for you. | | `name` | `string` | No | Unique name for this Machine. If omitted, one is generated for you | | `region` | `string` | No | The target region. Omitting this param launches in the same region as your WireGuard peer connection (somewhere near you). | | `skip_launch` | `boolean` | No | | | `skip_secrets` | `boolean` | No | | | `skip_service_registration` | `boolean` | No | | ## updateMetadataRequest | Property | Type | Required | Description | | - | - | - | - | | `machine_version` | `string` | No | | | `metadata` | `map` | No | | | `updated_at` | `string` | No | | ## updatePostgresUserRoleRequest | Property | Type | Required | Description | | - | - | - | - | | `role` | `string` | Yes | New role for the user. Enum: schema\_admin, writer, reader | ## UpdateVolumeRequest | Property | Type | Required | Description | | - | - | - | - | | `auto_backup_enabled` | `boolean` | No | | | `snapshot_retention` | `integer` | No | | ## upsertMetadataKeyRequest | Property | Type | Required | Description | | - | - | - | - | | `updated_at` | `string` | No | | | `value` | `string` | No | | ## VerifySecretkeyRequest | Property | Type | Required | Description | | - | - | - | - | | `plaintext` | `integer[]` | No | | | `signature` | `integer[]` | No | | ## Volume | Property | Type | Required | Description | | - | - | - | - | | `attached_alloc_id` | `string` | No | | | `attached_machine_id` | `string` | No | | | `auto_backup_enabled` | `boolean` | No | | | `block_size` | `integer` | No | | | `blocks` | `integer` | No | | | `blocks_avail` | `integer` | No | | | `blocks_free` | `integer` | No | | | `bytes_total` | `integer` | No | | | `bytes_used` | `integer` | No | | | `created_at` | `string` | No | | | `encrypted` | `boolean` | No | | | `fstype` | `string` | No | | | `host_features` | `string[]` | No | | | `host_status` | `string` | No | Enum: ok, unknown, unreachable | | `id` | `string` | No | | | `name` | `string` | No | | | `region` | `string` | No | | | `required_host_features` | `string[]` | No | | | `size_gb` | `integer` | No | | | `snapshot_retention` | `integer` | No | | | `state` | `string` | No | | | `type` | `string` | No | Enum: local, cache | | `zone` | `string` | No | | ## VolumeSnapshot | Property | Type | Required | Description | | - | - | - | - | | `created_at` | `string` | No | | | `digest` | `string` | No | | | `id` | `string` | No | | | `retention_days` | `integer` | No | | | `size` | `integer` | No | | | `status` | `string` | No | | | `volume_size` | `integer` | No | | ## WaitMachineResponse | Property | Type | Required | Description | | - | - | - | - | | `event_id` | `string` | No | | | `ok` | `boolean` | No | | | `state` | `string` | No | | | `version` | `string` | No | | # List All Machines Source: https://docs.fly.io/api/machines/organizations/list-all-machines /api/machines/openapi.json get /v1/orgs/{org_slug}/machines List all Machines associated with a specific organization. Machines are sorted by their `updated_at` timestamps, oldest to newest. This API call represents "a point in time". Recent machine changes, including creations and destructions, may take time to propagate. When polling with `updated_after`, offset your timestamps to catch late-arriving events. # List All Volumes Source: https://docs.fly.io/api/machines/organizations/list-all-volumes /api/machines/openapi.json get /v1/orgs/{org_slug}/volumes List all volumes for an organization with optional filters and cursor-based pagination. # Get Placements Source: https://docs.fly.io/api/machines/platform/get-placements /api/machines/openapi.json post /v1/platform/placements Simulates placing the specified number of machines into regions, depending on available capacity and limits. # Get Regions Source: https://docs.fly.io/api/machines/platform/get-regions /api/machines/openapi.json get /v1/platform/regions List all regions on the platform with their details. # Attach Cluster to App Source: https://docs.fly.io/api/machines/postgres-clusters/attach-cluster-to-app /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/attachments Record an attachment between a cluster and a Fly app. This endpoint records the relationship but does not set DATABASE_URL or other app secrets; configure the connection string separately. The attachment is created if it does not already exist. # Create Backup Source: https://docs.fly.io/api/machines/postgres-clusters/create-backup /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/backups Create a backup for a specific cluster. The backup runs asynchronously and is rejected if one is already in progress. # Create Database Source: https://docs.fly.io/api/machines/postgres-clusters/create-database /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/databases Create a database within a specific cluster. # Create Postgres Cluster Source: https://docs.fly.io/api/machines/postgres-clusters/create-postgres-cluster /api/machines/openapi.json post /v1/postgres Create a Managed Postgres cluster for the organization named in the request body. Provisioning is asynchronous: poll `GET /v1/postgres/{id}` until `status == ready` before calling database, user, extension, or credential endpoints. # Create User Source: https://docs.fly.io/api/machines/postgres-clusters/create-user /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/users Create a Postgres user within a specific cluster. Fetch the generated password from the credentials endpoint. # Delete Postgres Cluster Source: https://docs.fly.io/api/machines/postgres-clusters/delete-postgres-cluster /api/machines/openapi.json delete /v1/postgres/{postgres_cluster_id} Delete a Managed Postgres cluster. The cluster is marked for deletion and removed asynchronously. # Delete User Source: https://docs.fly.io/api/machines/postgres-clusters/delete-user /api/machines/openapi.json delete /v1/postgres/{postgres_cluster_id}/users/{username} Delete a specific Postgres user within a cluster. # Detach Cluster from App Source: https://docs.fly.io/api/machines/postgres-clusters/detach-cluster-from-app /api/machines/openapi.json delete /v1/postgres/{postgres_cluster_id}/attachments/{app_name} Detach a specific cluster from a Fly app. # Disable Extension Source: https://docs.fly.io/api/machines/postgres-clusters/disable-extension /api/machines/openapi.json delete /v1/postgres/{postgres_cluster_id}/databases/{database_name}/extensions/{extension_name} Disable a specific Postgres extension within a database, optionally dropping objects that depend on it. System extensions cannot be disabled. # Drop Database Source: https://docs.fly.io/api/machines/postgres-clusters/drop-database /api/machines/openapi.json delete /v1/postgres/{postgres_cluster_id}/databases/{database_name} Delete a specific database within a cluster, along with objects that depend on it. # Enable Extension Source: https://docs.fly.io/api/machines/postgres-clusters/enable-extension /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/databases/{database_name}/extensions Enable a Postgres extension within a specific database. PostGIS extensions require a PostGIS-enabled cluster. # Fork Cluster to New Cluster Source: https://docs.fly.io/api/machines/postgres-clusters/fork-cluster-to-new-cluster /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/fork Fork a ready Managed Postgres cluster into a new cluster that inherits the source's settings, on the source's plan unless `plan` is given. The source cluster is left unchanged, and the fork is provisioned asynchronously. # Get Postgres Cluster Source: https://docs.fly.io/api/machines/postgres-clusters/get-postgres-cluster /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id} Get details of a specific Managed Postgres cluster. # Get User Credentials Source: https://docs.fly.io/api/machines/postgres-clusters/get-user-credentials /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/users/{username}/credentials Get the connection credentials for a specific Postgres user within a cluster. # List Active Queries Source: https://docs.fly.io/api/machines/postgres-clusters/list-active-queries /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/queries/active List active queries in the supplied database. The database query parameter is required and must not be empty or whitespace-only. Returns full query text and durations in seconds. # List Backups Source: https://docs.fly.io/api/machines/postgres-clusters/list-backups /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/backups List all backups associated with a specific cluster. # List Databases Source: https://docs.fly.io/api/machines/postgres-clusters/list-databases /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/databases List all databases within a specific cluster. # List Extensions Source: https://docs.fly.io/api/machines/postgres-clusters/list-extensions /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/databases/{database_name}/extensions List all extensions available within a specific database, indicating which are installed. # List Postgres Clusters Source: https://docs.fly.io/api/machines/postgres-clusters/list-postgres-clusters /api/machines/openapi.json get /v1/postgres List Managed Postgres clusters for an organization. # List Postgres Plans Source: https://docs.fly.io/api/machines/postgres-clusters/list-postgres-plans /api/machines/openapi.json get /v1/postgres/plans List the Managed Postgres plans the caller can choose when creating or forking a cluster in an organization, cheapest first. # List Slow Queries Source: https://docs.fly.io/api/machines/postgres-clusters/list-slow-queries /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/queries/slow List the top 20 queries by cumulative total execution time, excluding the postgres database. Uses the latest available metrics within the lookback range; statistics are cumulative PostgreSQL counters, not deltas over that range. Returns full query text and execution times in seconds. # List Users Source: https://docs.fly.io/api/machines/postgres-clusters/list-users /api/machines/openapi.json get /v1/postgres/{postgres_cluster_id}/users List all Postgres users within a specific cluster. # Restore Backup or Point in Time to New Cluster Source: https://docs.fly.io/api/machines/postgres-clusters/restore-backup-or-point-in-time-to-new-cluster /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/restore Restore a backup, or a point in time within the cluster's PITR recovery window, into a new Managed Postgres cluster. The source cluster is left unchanged, and a new cluster is provisioned asynchronously. # Rotate User Password Source: https://docs.fly.io/api/machines/postgres-clusters/rotate-user-password /api/machines/openapi.json post /v1/postgres/{postgres_cluster_id}/users/{username}/rotate_password Rotate a specific Postgres user's password within a cluster, optionally terminating the user's existing sessions. # Update User Role Source: https://docs.fly.io/api/machines/postgres-clusters/update-user-role /api/machines/openapi.json patch /v1/postgres/{postgres_cluster_id}/users/{username} Update a specific Postgres user's role within a cluster. # Create or update a secret key Source: https://docs.fly.io/api/machines/secrets/create-or-update-a-secret-key /api/machines/openapi.json post /v1/apps/{app_name}/secretkeys/{secret_name} # Create or update Secret Source: https://docs.fly.io/api/machines/secrets/create-or-update-secret /api/machines/openapi.json post /v1/apps/{app_name}/secrets/{secret_name} Running Machines don't pick up the change on their own: create or update Machines with `min_secrets_version` set to the returned `version` for them to see it. # Decrypt with a secret key Source: https://docs.fly.io/api/machines/secrets/decrypt-with-a-secret-key /api/machines/openapi.json post /v1/apps/{app_name}/secretkeys/{secret_name}/decrypt # Delete an app secret Source: https://docs.fly.io/api/machines/secrets/delete-an-app-secret /api/machines/openapi.json delete /v1/apps/{app_name}/secrets/{secret_name} Running Machines don't pick up the change on their own: create or update Machines with `min_secrets_version` set to the returned `version` for them to see it. # Delete an app's secret key Source: https://docs.fly.io/api/machines/secrets/delete-an-apps-secret-key /api/machines/openapi.json delete /v1/apps/{app_name}/secretkeys/{secret_name} # Encrypt with a secret key Source: https://docs.fly.io/api/machines/secrets/encrypt-with-a-secret-key /api/machines/openapi.json post /v1/apps/{app_name}/secretkeys/{secret_name}/encrypt # Generate a random secret key Source: https://docs.fly.io/api/machines/secrets/generate-a-random-secret-key /api/machines/openapi.json post /v1/apps/{app_name}/secretkeys/{secret_name}/generate # Get an app secret Source: https://docs.fly.io/api/machines/secrets/get-an-app-secret /api/machines/openapi.json get /v1/apps/{app_name}/secrets/{secret_name} # Get an app's secret key Source: https://docs.fly.io/api/machines/secrets/get-an-apps-secret-key /api/machines/openapi.json get /v1/apps/{app_name}/secretkeys/{secret_name} # List app secrets belonging to an app Source: https://docs.fly.io/api/machines/secrets/list-app-secrets-belonging-to-an-app /api/machines/openapi.json get /v1/apps/{app_name}/secrets # List secret keys belonging to an app Source: https://docs.fly.io/api/machines/secrets/list-secret-keys-belonging-to-an-app /api/machines/openapi.json get /v1/apps/{app_name}/secretkeys # Sign with a secret key Source: https://docs.fly.io/api/machines/secrets/sign-with-a-secret-key /api/machines/openapi.json post /v1/apps/{app_name}/secretkeys/{secret_name}/sign # Update app secrets belonging to an app Source: https://docs.fly.io/api/machines/secrets/update-app-secrets-belonging-to-an-app /api/machines/openapi.json post /v1/apps/{app_name}/secrets Running Machines don't pick up the change on their own: create or update Machines with `min_secrets_version` set to the returned `version` for them to see it. # Verify with a secret key Source: https://docs.fly.io/api/machines/secrets/verify-with-a-secret-key /api/machines/openapi.json post /v1/apps/{app_name}/secretkeys/{secret_name}/verify # Check DNS and re-validate certificate Source: https://docs.fly.io/api/machines/tls-certificates/check-dns-and-re-validate-certificate /api/machines/openapi.json post /v1/apps/{app_name}/certificates/{hostname}/check # Get certificate details Source: https://docs.fly.io/api/machines/tls-certificates/get-certificate-details /api/machines/openapi.json get /v1/apps/{app_name}/certificates/{hostname} # List certificates for app Source: https://docs.fly.io/api/machines/tls-certificates/list-certificates-for-app /api/machines/openapi.json get /v1/apps/{app_name}/certificates # Remove ACME certificates Source: https://docs.fly.io/api/machines/tls-certificates/remove-acme-certificates /api/machines/openapi.json delete /v1/apps/{app_name}/certificates/{hostname}/acme # Remove certificate Source: https://docs.fly.io/api/machines/tls-certificates/remove-certificate /api/machines/openapi.json delete /v1/apps/{app_name}/certificates/{hostname} # Remove custom certificate Source: https://docs.fly.io/api/machines/tls-certificates/remove-custom-certificate /api/machines/openapi.json delete /v1/apps/{app_name}/certificates/{hostname}/custom # Request ACME certificate Source: https://docs.fly.io/api/machines/tls-certificates/request-acme-certificate /api/machines/openapi.json post /v1/apps/{app_name}/certificates/acme # Upload custom certificate Source: https://docs.fly.io/api/machines/tls-certificates/upload-custom-certificate /api/machines/openapi.json post /v1/apps/{app_name}/certificates/custom # Authenticate token header Source: https://docs.fly.io/api/machines/tokens/authenticate-token-header /api/machines/openapi.json post /v1/tokens/authenticate Verify a token header without checking resource access. # Authorize token for resource access Source: https://docs.fly.io/api/machines/tokens/authorize-token-for-resource-access /api/machines/openapi.json post /v1/tokens/authorize Verify a token header and validate it against a requested access scope. # Get Current Token Information Source: https://docs.fly.io/api/machines/tokens/get-current-token-information /api/machines/openapi.json get /v1/tokens/current Get information about the current macaroon token(s), including organizations, apps, user identity hashes, and machine restrictions # Request a Petsem token for accessing KMS Source: https://docs.fly.io/api/machines/tokens/request-a-petsem-token-for-accessing-kms /api/machines/openapi.json post /v1/tokens/kms # Request an OIDC token Source: https://docs.fly.io/api/machines/tokens/request-an-oidc-token /api/machines/openapi.json post /v1/tokens/oidc Request an Open ID Connect token for your machine. Customize the audience claim with the `aud` parameter. This returns a JWT token. Learn more about [using OpenID Connect](/docs/reference/openid-connect/) on Fly.io. # Create Snapshot Source: https://docs.fly.io/api/machines/volumes/create-snapshot /api/machines/openapi.json post /v1/apps/{app_name}/volumes/{volume_id}/snapshots Create a snapshot for a specific volume within an app. # Create Volume Source: https://docs.fly.io/api/machines/volumes/create-volume /api/machines/openapi.json post /v1/apps/{app_name}/volumes Create a volume for a specific app using the details provided in the request body. # Destroy Volume Source: https://docs.fly.io/api/machines/volumes/destroy-volume /api/machines/openapi.json delete /v1/apps/{app_name}/volumes/{volume_id} Delete a specific volume within an app by volume ID. # Extend Volume Source: https://docs.fly.io/api/machines/volumes/extend-volume /api/machines/openapi.json put /v1/apps/{app_name}/volumes/{volume_id}/extend Extend a volume's size within an app using the details provided in the request body. # Get Volume Source: https://docs.fly.io/api/machines/volumes/get-volume /api/machines/openapi.json get /v1/apps/{app_name}/volumes/{volume_id} Retrieve details about a specific volume by its ID within an app. # List Snapshots Source: https://docs.fly.io/api/machines/volumes/list-snapshots /api/machines/openapi.json get /v1/apps/{app_name}/volumes/{volume_id}/snapshots List all snapshots for a specific volume within an app. # List Volumes Source: https://docs.fly.io/api/machines/volumes/list-volumes /api/machines/openapi.json get /v1/apps/{app_name}/volumes List all volumes associated with a specific app. # Update Volume Source: https://docs.fly.io/api/machines/volumes/update-volume /api/machines/openapi.json put /v1/apps/{app_name}/volumes/{volume_id} Update a volume's configuration using the details provided in the request body. # Chat: A 6PN Example with NATS Source: https://docs.fly.io/app-guides/6pndemochat > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # 6pn-demo-chat A chat server that uses Fly 6PN networking to connect to a NATS cluster enabling global chat. ## Rationale This is the code for a very simple chat example that uses bare websockets in Node and a Browser to connect to it. Instead of immediately broadcasting the message to all users, the server sends the message on to a NATS server, using 6PN private networking, which broadcasts the message to all interested parties. ## Preparation As this example relies on a NATS server (or cluster) to be available, it should be deployed alongside the [`nats-cluster`](https://github.com/fly-examples/nats-cluster) example, within the same Fly organization. Make a note of the app-name used to deploy the NATS cluster. Select a name for your chat app. ## Initializing Create an app on Fly with your chosen chat app name, using `fly init`: ```bash theme={null} fly init YOURAPPNAME --import example-fly.toml ``` ## Deploying To deploy this app, we have one detail we have to pass it, the name fo the NATS cluster. The Dockerfile we use takes a build argument (NATSAPP) and turns it into an environment variable for the app to pick up when it runs. So if your NATS cluster app is called `myclusterapp` then running: ```bash theme={null} fly deploy --build-arg NATSAPP=myclusterapp ``` Note: You can make that setting permanent by adding ```toml theme={null} [build.args] NATSAPP="myclusterapp" ``` to the fly.toml file. Once deployed, open up the web page - `fly open` will get you there quickly - enter a chat handle and begin chatting. ## Notes The more interesting parts of this app happen out of sight of the users. It's worth following the path of the NATSAPP build argument to find out more. * NATSAPP's value is passed as a build argument to the Dockerfile at deployment time. * Inside the Dockerfile, the build argument is converted into an Environment variable, `NATSAPPHOST` which is retained with the image's configuration. * When the `server.js` app starts up, it picks up the `NATSAPPHOST` variable: ``` let natsapphost=process.env.NATSAPPHOST ``` * It then uses that value to connect to the NATS cluster: ``` const nc = NATS.connect(natsapphost+".internal"); ``` The combination of an app name and `.internal` in 6PN networking resolves to the IPv6 addresses of the first instance of all instances if that app currently running. So, if we set NATSAPP to `myclusterapp` then scaling and adding nodes to `myclusterapp` would automatically be reflected in `myclusterapp.internal`. 6PN has a number of `.internal` addresses an app can query which you can read about in the [6PN documentation](/networking/private-networking#fly-io-internal-dns). Once the chat server has connected to the NATS cluster, it subscribes to a topic `msg` and any message written to `msg` will be broadcast to all web socket clients that are connected. If any of those web socket clients sends a message to the chat server, then it will publish that message to the `msg` topic on the NATs cluster. It doesn't even try to echo it locally, relying on the NATS cluster to publish to subscribers. The NATS cluster can be scaled up and down according to demand. Chat servers will always be able to locate an available node thanks to Fly 6PN networking advertising homogenous services through DNS and `.internal` addresses. You can then deploy the NATS cluster and/or the chat server to any region and know that both will have predictable names; the NATS cluster through Fly's 6PN networking, and the chat server through Fly's AnyCast Edge network. ## Discuss You can discuss this example (and the nats-cluster example) on the [dedicated Fly Community topic](https://community.fly.io/t/new-examples-nats-cluster-and-6pn-demo-chat/562). # EdgeDB on Fly Source: https://docs.fly.io/app-guides/edgedb Run EdgeDB on Fly.io backed by a Fly Postgres app: set EDGEDB_SERVER_* secrets, attach Postgres, grant role permissions, and persist the TLS cert. [EdgeDB](https://www.edgedb.com) is a [graph-relational database](https://www.edgedb.com/blog/the-graph-relational-database-defined) that runs on top of Postgres and is designed as a spiritual successor to SQL. This guide explains how to perform a single-region deployment of EdgeDB with persistent storage. It will require two apps: one for Postgres and one for the EdgeDB container. ## Create the Postgres app Create a Postgres app. You'll need to replace `mypostgres` with a unique name of your choosing. ```bash cmd theme={null} flyctl postgres create \ --name mypostgres \ --vm-size performance-1x \ --volume-size 10 \ --initial-cluster-size 1 ``` ```output output theme={null} Automatically selected personal organization: ? Select region: sea (Seattle, Washington (US)) Creating postgres cluster mypostgres in organization personal Postgres cluster mypostgres created Username: postgres Password: Hostname: mypostgres.internal Proxy Port: 5432 PG Port: 5433 Save your credentials in a secure place, you won't be able to see them again! ``` ## Create the EdgeDB app Create a directory and create a new app. Be sure to replace `myedgedb` with a custom name. When prompted, pick a region and a Fly organization. ```bash theme={null} flyctl launch --name myedgedb --image edgedb/edgedb --no-deploy ``` Since we're using `flyctl launch`, this will create a `fly.toml`. All `flyctl` commands will now be applied against our `myedgedb` application unless otherwise specified with `--app`. EdgeDB needs more RAM than the default `256MB` so let's scale the RAM to `1024MB`. ```bash theme={null} flyctl scale memory 1024 ``` Set a password for the EdgeDB instance as a Fly secret. Keep track of this password; it won't be visible again. ```bash theme={null} flyctl secrets set EDGEDB_PASSWORD=mysecretpassword ``` We need to configure a few other environment variables too. ```bash theme={null} flyctl secrets set \ EDGEDB_SERVER_BACKEND_DSN_ENV=DATABASE_URL \ EDGEDB_SERVER_TLS_CERT_MODE=generate_self_signed \ EDGEDB_SERVER_PORT=8080 ``` Let's discuss what's going on with all these secrets. * `EDGEDB_SERVER_BACKEND_DSN_ENV` tells EdgeDB which environment variable holds the Postgres connection string. * `EDGEDB_SERVER_TLS_CERT_MODE` tells EdgeDB to auto-generate a self-signed TLS certificate. * You may instead choose to provision a custom TLS certificate. In this case, you should instead create two other secrets: assign your certificate to `EDGEDB_SERVER_TLS_CERT` and your private key to `EDGEDB_SERVER_TLS_KEY`. * `EDGEDB_SERVER_PORT` tells EdgeDB to listen on port `8080` instead of the default 5656, as Fly uses `8080` for its default health checks. ## Attach and deploy Let's attach `mypostgres` to `myedgedb`. ```bash theme={null} flyctl postgres attach mypostgres ``` This sets the value of `DATABASE_URL` in the `myedgedb` app and creates a new role called `myedgedb` in our Postgres database. By default, this role doesn't have the full set of permissions EdgeDB needs to run. To change that, open a connection to your Postgres instance. ```bash cmd theme={null} flyctl postgres connect --app mypostgres ``` ```output output theme={null} Connecting to mypostgres.internal... complete psql (14.4 (Debian 14.4-1.pgdg110+1)) Type "help" for help. postgres=# ``` Then run the following REPL command. ``` postgres=# alter role myedgedb createrole createdb; ALTER ROLE postgres=# \quit ``` Finally, our EdgeDB app is ready to deploy! ```bash theme={null} flyctl deploy ``` It may take a few minutes to fully deploy. Debug with `flyctl logs` and check the deployment status with `flyctl status`. ```bash theme={null} flyctl status ``` ## Persist certificates We need to persist the auto-generated TLS certificate to make sure it survives EdgeDB app restarts. (If you’ve provided your own certificate, skip this step.) ```bash theme={null} flyctl ssh console \ -C "edgedb-show-secrets.sh --format=toml EDGEDB_SERVER_TLS_KEY EDGEDB_SERVER_TLS_CERT" \ | tr -d '\r' | flyctl secrets import ``` ## Connection You'll be able to connect to your EdgeDB instance using the following DSN from *other Fly.io apps* in the same organization: `edgedb://edgedb:mysecretpassword@myedgedb.internal:8080` To pass this DSN to another app, create a secret called `EDGEDB_DSN` containing this value. EdgeDB's client libraries will read this environment variable and connect to your instance automatically. ```bash theme={null} flyctl secrets set \ EDGEDB_DSN=edgedb://edgedb:mysecretpassword@myedgedb.internal:8080 \ --app my-other-app ``` If you've [configured a Wireguard tunnel](/networking/private-networking) on your local machine, you'll be able to open a REPL to your new EdgeDB instance with the `edgedb` CLI. ```bash cmd theme={null} edgedb --dsn edgedb://edgedb:mysecretpassword@myedgedb.internal:8080 --tls-security insecure ``` ```output output theme={null} EdgeDB 2.x (repl 2.x.x) Type \help for help, \quit to quit. edgedb> ``` # Fauna and Fly.io Source: https://docs.fly.io/app-guides/fauna Pair a Fly.io app with a Fauna database: Fauna starter kits, US and EU Region Groups, and which Fly regions to scale into with fly scale count. [Fauna](https://fauna.com/) is a distributed relational database with a document data model, and delivered as an API. Databases created in Fauna automatically get three geographically distributed replicas with active-active writes and ACID compliance, making it a powerful complement to Fly.io in serving low latency reads and writes for dynamic global applications. ## Starter kit Fauna provides [starter kits](https://github.com/orgs/fauna-labs/repositories?q=fly-io-starter) to help you quickly launch an app on Fly.io, using Fauna as the database backend. Before you start, read the short section below to learn how Fauna and Fly.io makes for a great combination when building low latency, dynamic global applications. ## Region Groups A Fauna Region Group refers to the locality footprint of where the replicas are located, and allows you to control where your data resides, making it possible to comply with data locality legislation, such as the General Data Protection Regulation ([GDPR](https://gdpr-info.eu/)). ![Fauna Region Group](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/fauna_region_groups.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=c90a5b1c86f8b60048dfc570b561ee13) When you create a database in Fauna, you choose which Region Group you want the database created in. When you query the database, your request is automatically routed to the closest replica (within the Region Group) based on latency. This functionality comes out-of-the-box without any special configuration or deployment strategy. ## Scale your deployment to match the Fauna footprint Currently, Fauna provides 2 choices of Regions Groups, US and EU. The table below lists the Fly.io regions that are closest to the Fauna replicas of each respective Region Group: | Fauna Region Group | Deploy on Fly Regions | | - | - | | EU | lhr, arn, fra | | US | sjc, ord, iad | To take full advantage of Fauna's distributed footprint, you should deploy your app on 3 Fly.io regions as well, and as close as possible to the Region Groups' replicas. For example, let's say you created your Fauna database in the US Region Group and you used the Fauna [Python Fly.io starter kit](https://github.com/fauna-labs/python-fly-io-starter) with the default configuration to create your Fly app. Your app will be in the `sjc` region, as determined by `primary_region` in the `fly.toml` config file. You can add Machines in the other closest US regions using this command: ``` fly scale count 2 --region ord,iad ``` Run `fly scale show` to see where your app's Machines are running. For example: ```bash cmd theme={null} fly scale show ``` ```output output theme={null} VM Resources for app: my-app-name Groups NAME COUNT KIND CPUS MEMORY REGIONS app 3 shared 1 256 MB iad,ord,sjc ``` # Appkata: Gogs - standalone Git Server Source: https://docs.fly.io/app-guides/git-gogs-server > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # appkata-gogs Deploying Gogs using Fly. [Read this guide on Fly.](/app-guides/git-gogs-server) ## Rationale **Appkata**: The application fighting style - and a series of application-centric examples for Fly.io. The fundamental tool of modern development is a version control system, most often Git. Let's get some Git on Fly. If we just skip past creating a bare Git server because that's no fun, we find Gogs. Gogs bills itself as "a painless self-hosted Git service" and comes ready-packed as an easier to install, and complete Git service, including a web front end for issues and pull requests. Let's get Gogs going on Fly. Our starting point is [gogs/docker](https://github.com/gogs/gogs/tree/main/docker) which has all the configuration and files related to the official Gogs docker image. It's that image we are going to build with. We are going to use that image to deploy on Fly. ## Databases Gogs needs the support of a database, typically MySQL or Postgres, which would involve deploying a whole separate process. But it also supports using SQLite3 as a local database. The database will need persistent disk space. We'll use the Fly Volumes feature to give our application that disk space. ## Ports Gogs runs a web service on port 3000 and an SSH service on port 22. We'll get Fly to redirect the web service to port's 80 and 443, and put SSH on port 10022. First though, some initialization ```bash cmd theme={null} > fly init appkata-gogs --image gogs/gogs --org personal --port 3000 ``` ```out out theme={null} Selected App Name: appkata-gogs New app created Name = appkata-gogs Organization = personal Version = 0 Status = Hostname = App will initially deploy to lhr (London, United Kingdom) region ``` This generates a `fly.toml` file with the basic components for configuring our gogs: ```toml theme={null} # fly.toml file generated for appkata-gogs on 2020-11-02T16:00:06Z app = "appkata-gogs" [build] image = "gogs/gogs" [[services]] internal_port = 3000 protocol = "tcp" [services.concurrency] hard_limit = 25 soft_limit = 20 [[services.ports]] handlers = ["http"] port = "80" [[services.ports]] handlers = ["tls", "http"] port = "443" [[services.tcp_checks]] interval = 10000 timeout = 2000 ``` This is the barebones of a configuration. It only redirects port 3000. The next step is to add a service to handle SSH communications from Gogs. Append this to `fly.toml`: ```toml theme={null} [[services]] internal_port = 22 protocol = "tcp" [[services.ports]] handlers = [] port = 10022 [[services.tcp_checks]] interval = 10000 timeout = 2000 ``` This passes traffic on 10022 through to Gogs on port 22. There's no handler needed for this, but it is checked as part of the Fly health checks. ## Persistent Disks With the other port set up, we then move to the persistent data storage. Make a Fly volume in the region where the app will be running. When we "inited" the app, it told us it would initially be deployed to London, LHR region. Let's create a default (10GB) sized volume called `data` there: ```bash cmd theme={null} fly volumes create data --region lhr ``` ```out out theme={null} ID: JD20D7DMOVAequGbLPR Name: data Region: lhr Size GB: 10 Created at: 04 Nov 20 09:45 UTC ``` The Gogs image expects to see its data volume mounted on /data. To do that, it's back to `fly.toml` to add a `mounts` section for our new volume: ```toml theme={null} [[mounts]] source="data" destination="/data" ``` ## Deploying We can now deploy Gogs using the command `fly deploy.` ```bash cmd theme={null} ❯ fly deploy ``` ```out out theme={null} Deploying appkata-gogs ==> Validating App Configuration --> Validating App Configuration done Services TCP 80/443 ⇢ 3000 TCP 10022 ⇢ 22 Deploying image: gogs/gogs --> docker.io/gogs/gogs ==> Optimizing Image --> Done Optimizing Image ==> Creating Release Release v0 created Deploying to : appkata-gogs.fly.dev Monitoring Deployment You can detach the terminal anytime without stopping the deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 2 passing] --> v0 deployed successfully ``` Note the two health-checks which match up with the two health-checks in `fly.toml`. Gogs is now up and running and you can use `fly open` to go straight to the site where you'll be greeted by the first time run screen. Now it's time to configure our Gogs deployment. ## Custom Domains If you have a custom domain you want to use for your Gogs server, now is the time to set it up. You'll need to edit some DNS records so kick off the process with `fly certs add yourdomain` and it'll give you instructions as you go. ## Configuring Gogs The first stop on the install screen for Gogs is the database settings. As mentioned at the start, we want to set this to SQLlite3, which is nicely self-contained. We'll also want to make sure that the path is `/data/gogs.db` (an absolute path, not a relative one). ![Database Settings](https://raw.githubusercontent.com/fly-examples/appkata-gogs/main/images//dbsettings.png) Scroll down and you'll come the application settings. We'll want to change the domain to the Fly domain (or your custom domain name). You'll also want to change the ssh port to the one we set in the fly.toml services (10022). The HTTP port doesn't need to change but the application URL should change to the same domain as the application URL and there's no need to set a port in the URL. ![App Settings](https://raw.githubusercontent.com/fly-examples/appkata-gogs/main/images/appsettings.png) Finally, make sure to create your admin account otherwise anyone logging in first to the server will get access and thats not good. ![Admin Settings](https://raw.githubusercontent.com/fly-examples/appkata-gogs/main/images/adminsettings.png) Click to install Gogs and it'll be set up with this configuration. You can now go back to the front page - run `fly open` if you can't remember the URL - and log in. You now have your own Git repository, issues tracker and more. ## Notes * Once set up, the configuration is effectively frozen - remember this when setting up. The clear the data and configuration, `fly suspend` the app, destroy the existing volume and create a new similarly named volume, then `fly resume` the app. ## Discuss * You can discuss this example on the [community.fly.io](https://community.fly.io/t/gogs-standalone-git-service-as-a-fly-example/358) topic. # Run an NGINX proxy on Fly Source: https://docs.fly.io/app-guides/global-nginx-proxy > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** Everything starts with a Fly app on Fly. If you already have an application running somewhere else, and want to get some of the benefits of running it close to users, you can launch a proxy app on Fly. This will forward requests to your *origin* application for processing. ### Configuring NGINX NGINX is a powerful HTTP proxy server you can use to direct traffic to its appropriate destination, apply routing rules, or even implement caching. We start with a Dockerfile that'll allow us to create a NGINX image with our own `nginx.conf` configuration file: ``` FROM nginx COPY nginx.conf /etc/nginx/conf.d/nginx.conf ``` That's it. It copies a local configuration file into the image. In this example config, we want to proxy requests from example.com to realexample.com and proxy requests from exemplum.com to realexemplum.com. ``` server { listen 8080; listen [::]:8080; server_name example.com; location / { proxy_pass https://realexample.com/; proxy_set_header X-Forwarded-Host $http_host; } } server { listen 8080; listen [::]:8080; server_name exemplum.com; location / { proxy_pass https://realexemplum.com/; proxy_set_header X-Forwarded-Host $http_host; } } ``` Both server sections are set to listen on port 8080. The `server_name` directive says which hostname the server directive will be handling and the `location` directive uses `proxy_pass` to send all requests on to the appropriate site. ### Deploying NGINX to Fly We now need to create a slot for our new Fly app. Assuming you are signed up and logged in (if not see our [hands-on for getting started](/getting-started/launch-demo)), then we'll run this: ```bash theme={null} flyctl apps create ``` This command will prompt us for an app name - we'll use `nginxproxy` but we recommend you use the autogenerated name to ensure a unique name. As well as creating the slot for the app in the Fly infrastructure, a `fly.toml` file will be created with the app name and configuration settings in it. To deploy our NGINX, we literally issue the deploy command: ```bash theme={null} flyctl deploy ``` Our app will now be built and deployed onto the Fly infrastructure. When that's complete, get the status of the application with `flyctl status`. You'll see it has a hostname which you can use to connect. The host name will match none of the hosts in the `nginx.conf` file and so it will proxy for the default server - the first server listed in that configuration file. ### Choose your own adventure With the proxy app in place, you now have a foundation for adding features "at the edge". Maybe try HTTP caching in NGINX, or look at our [OpenResty guide and learn to write Lua](/app-guides/openresty-nginx-plus-lua). Or if you're feeling extra spicy, let your customers [add their own domains](/networking/custom-domain) to your application. # Apollo GraphQL + Edge Caching Source: https://docs.fly.io/app-guides/graphql-edge-caching-apollo > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Edge GraphQL with Apollo Server and Fly.io > Run Apollo Server + Caching close to users with [Fly](https://fly.io/). ## Overview This application demonstrates how to use Fly, Redis and Apollo to enable rapid GraphQL queries from a variety of data sources. Fly runs tiny virtual machines close to your users, and includes a global Redis cache. Apollo is a GraphQL server that integrates with a variety of data sources and can be run in Node. This application combines these three technologies to create a GraphQL server on Fly. It uses the [Open Library API](https://openlibrary.org/developers/api) as a backend source to serve two GraphQL endpoints, and caches responses in Fly's Redis instance. ## Deploying to Fly * Install [flyctl](/flyctl/install) * Login to Fly: `flyctl auth login` * Create a new Fly app: `flyctl apps create` * Deploy the app to Fly: `flyctl deploy` Once deployed, you can launch the GraphQL Playground by running `flyctl open`. ## Running GraphQL queries To search books, enter: ``` { search(search:"for whom the bell tolls") { isbn author_name contributor title first_publish_year } } ``` To get a single book by ISBN, enter: ``` { book(bib_key:"ISBN:0385472579") { bib_key, thumbnail_url, preview_url, info_url } } ``` Learn more about GraphQL playground [in the documentation](https://www.apollographql.com/docs/apollo-server/testing/graphql-playground/). If you prefer to use to command line, there's two scripts, [`scripts/bookfind.sh`](https://github.com/fly-examples/edge-apollo-cache/blob/master/scripts/bookfind.sh) and [`scripts/booksearch.sh`](https://github.com/fly-examples/edge-apollo-cache/blob/master/scripts/booksearch.sh) you can use. Set the environment variable APPNAME to your applications name- you can find the application name with `flyctl info`. ## What Happens Inside The Application There are a number of parts of the server which are mostly defining the configuration of the packages used. `apollo-server` handles the web interactions, `apollo-server-cache-redis` is a component for that server which manages Redis as a general purpose cache and `apollo-server-plugin-response-cache` uses that cache for the particular job of caching responses from API requests. When a query arrives at the ApolloServer, it is filtered through the GraphQL types definition `typeDefs` and then matched with resolvers defined in `resolvers`. Both define the endpoints in terms of GraphQL types - for parsing the query - and in terms of datasources where the query can be resolved. In this case there are two queries, book and search. The resolvers definition maps each of these to the same RESTDataSource which is configured in books-api.js. It's there that the connection to the openlibrary.org REST API is defined. Once the REST query has been fulfilled, the server will return the results as JSON to the client that made the query. And in the background if its been configured to cache that result, it'll cache that response. Needless to say, it will also check on incoming queries to see if it already has a cached result to return. In `index.js`, after the definition of the `typeDefs` and `resolvers`, the application has code to create the RedisCache, either locally or using Fly's global Redis service. Environment variables with a connection string to the server determine which. Finally an ApolloServer is created, is given all the configured items, the `typedefs`, `resolvers`, a list of datasources, a cache with parameters and the `responseCachePlugin` and the server is set off listening for incoming requests. If you are wondering where the GraphQL Playground comes from in this, that's an integrated part of the Apollo Server. ## How Does Fly Fit Into This? Traditionally, an application like this would run somewhere in the cloud and usually distant from its users thanks to geography. The biggest problem then is the latency of the connection between the user and the application. As we previously mentioned, Fly runs tiny virtual machines close to users in datacenters around the world. Some of these machines handle connections, terminating TLS as close to the user as possible. As TLS needs a couple of connections to get going, thats a big boost, especially with an API. The connection handlers then pass the connection on to the application itself. Applications are also run all over the world and Fly lets you put your applications in the most appropriate regions for your task. An edge cache like this could be located in the locations where you are finding the most traffic. When there is no immediatly local application, Fly automatically finds the closest place the application is running and directs connections to that place. Edge hosting (and Fly specifically) offers significant performance benefits over traditional centralized hosting. Because requests can be served from the node closest to the user, responses will be much faster. This example uses Redis to cache responses so after the first request is made to a region, duplicate requests in the next hour will be significantly faster. ## Testing latency with cURL If you're running on MacOS or Linux, you likely already have the cURL command line tool installed. cURL can print timing data for requests, here are examples that show total request time, TCP connect time, and TLS handshake time. #### GraphQL cURL request See [`scripts/timegraphqlapi.sh`](https://github.com/fly-examples/edge-apollo-cache/blob/master/scripts/timegraphqlapi.sh) ```curl theme={null} curl 'https://.fly.dev/' \ -o /dev/null -sS \ -X POST \ -H "Content-Type: application/json" \ -d '{"operationName":null,"variables":{},"query":"{ book(bib_key: \"ISBN:0385472579\") {\n bib_key\n thumbnail_url\n preview_url\n info_url\n }\n}\n"}' \ -w "Timings\n------\ntotal: %{time_total}\nconnect: %{time_connect}\ntls: %{time_appconnect}\n" ``` #### Source API cURL request See [`scripts/timesourceapi.sh`](https://github.com/fly-examples/edge-apollo-cache/blob/master/scripts/timesourceapi.sh) ```curl theme={null} curl 'https://openlibrary.org/api/books?bibkeys=ISBN:0385472579' \ -o /dev/null -sS \ -w "Timings\n------\ntotal: %{time_total}\nconnect: %{time_connect}\ntls: %{time_appconnect}\n" ``` ## Latency Results Here are some sample response times using this app hosted on Fly vs. making requests to the Open Library API directly: | Request Method | Test 1 | Test 2 | Test 3 | | - | - | - | - | | [Open Library API](#source-api-curl-request) | 2.06s | 1.70s | 1.24s | | [Fly.io (uncached)](#graphql-curl-request) | 1.01s | 1.03s | 2.27s | | [Fly.io (cached)](#graphql-curl-request) | 0.13s | 0.11s | 0.09s | On uncached requests, Fly.io must connect to the Open Library API, but subsequent requests will load data from the regional Fly cache. You can try these tests locally using [cURL](#testing-latency-with-curl) # GraphQL on Fly with Hasura Source: https://docs.fly.io/app-guides/graphql-on-fly-with-hasura > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** So you have a PostgreSQL database somewhere in the cloud and you want to modernize how you access it. GraphQL is likely on your list of things you want to implement, but tools like Hasura - which take care of the backend complexity - need to be installed on a server somewhere. Fly can help by putting that server where it needs to be and making API requests faster than ever. Let's assume you have a Postgres database somewhere... *We made life easy and have our own sample app [FlyDictionary](https://github.com/fly-apps/flydictionary) with its own Deploy to Heroku button. Click that to create your own Heroku app and a Postgres backend.* And that you can get a URL with PostgreSQL user credentials to connect to that database. *For Heroku users, you can find those credentials by going to the Data view and selecting the database you want to use in the list. Then select the *Settings* tab and click on *View Credentials*. This will reveal a panel of various settings; the one you want is labelled *URI*.* Let's say that we got something like: ``` postgres://user:pass@serverhost.com:5432/databasename ``` We're now ready to deploy Hasura. ## Create an App For any Fly application, you need to reserve an app name. So let's create an app using `flyctl` the command line for Fly. ```bash theme={null} flyctl apps create ``` **If you don't have `flyctl`, you'll need to install it. Check out our [Install Flyctl](/flyctl/install) guide. If you haven't got a Fly account yet, run `flyctl auth signup` to get your own free account.** You will be asked for an app name at this point. Hit return to have one auto-generated for you. You'll see the app name when the create has been completed like this: ``` ❯ flyctl apps create ? App Name (leave blank to use an auto-generated name) ? Select organization: demo (demo) New app created Name = still-dust-14 Owner = demo Version = 0 Status = Hostname = Wrote config file fly.toml ``` So, now we have a Fly app named `still-dust-14`. ## Set the Stage Now, before we deploy Hasura, there's one more step for a smooth deployment. We are going to set some environment variables and secrets in the app. In particular, we're going to pass over our database URL to the soon to be deployed application. For this we use `flyctl secrets set`. ```bash theme={null} flyctl secrets set \ HASURA_GRAPHQL_DATABASE_URL="postgres://user:pass@serverhost.com:5432/databasename" \ HASURA_GRAPHQL_ADMIN_SECRET="OurAdminSecret" \ HASURA_GRAPHQL_ENABLE_CONSOLE=true ``` The Admin secret helps secure the Hasura GraphQL endpoint and console. For finer tuned authentication and role based security, consult the Hasura documentation. The `HASURA_GRAPHQL_ENABLE_CONSOLE` environment variable, set to true, will allow you to log in to Hasura's interactive console where you can create queries. Now, technically this whole step could take place after the app had deployed but that would mean allowing the app to spin its wheels looking for a database until we did set the values. This way, Hasura can start running properly as soon as it is deployed. ## Fly Deploy We can now deploy Hasura. We'll use the -i option of `flyctl deploy` which allows you to install publicly available images from Docker hub. ```bash theme={null} flyctl deploy -i hasura/graphql-engine:v1.1.0 ``` The Fly platform will now go and deploy the Hasura docker image on one of the Fly nodes around the world. When the deployment process has completed, running `flyctl info` will give the details of our Hasura instance: ``` ❯ flyctl info App Name = still-dust-14 Owner = dj Version = 2 Status = running Hostname = still-dust-14.fly.dev Services TASK PROTOCOL PORTS app tcp 80 => 8080 [HTTP] 443 => 8080 [TLS, HTTP] IP Addresses TYPE ADDRESS CREATED AT v4 77.83.141.82 1h16m ago v6 2a09:8280:1:ad2e:fa50:9986:a226:99d3 1h16m ago ``` The important part here is the App hostname - here it's `still-dust-14.fly.dev`. ## Connecting to Hasura Open your browser and go to the `/console` path on that host - `still-dust-14.fly.dev/console`. You'll be prompted for the admin secret we set in the secrets and once entered, you'll be in Hasura's GraphQL console. From there, you'll be able to create new tables in PostgreSQL, track existing tables and model your GraphQL API. Once you've done that, you can then create GraphQL queries. Of course, this wouldn't be how you would use Hasura day to day. You'd likely be using it as a GraphQL proxy for your applications. ``` ❯ curl -X POST -H "Content-Type: application/json" -H "x-hasura-admin-secret:OurAdminSecret" --data '{ "query": "{ example { id name } }" }' https://still-dust-14.fly.dev/v1/graphql {"data":{"example":[{"id":1,"name":"Fred"}, {"id":2,"name":"Wilma"}, {"id":3,"name":"Barney"}, {"id":4,"name":"Betty"}]}} ``` Once you do have your API configured, you can shut down the Hasura console by changing the console secret: ```bash theme={null} flyctl secrets set HASURA_GRAPHQL_ENABLE_CONSOLE=false ``` Fly will automatically redeploy Hasura with the console disabled, while still responding to your GraphQL API requests. # Deploy gRPC and gRPC-Web Services Source: https://docs.fly.io/app-guides/grpc-and-grpc-web-services > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # gRPC & gRPC-Web Services on Fly.io > Run gRPC & gRPC-Web services close to users with [Fly](https://fly.io/). ## Overview This application demonstrates how to run a gRPC service on Fly.io. Fly runs tiny virtual machines at edge datacenters close to your users. gRPC is designed for fast, low overhead services with client and server code generation tools for many languages and VMs. gRPC servers run their own HTTP/2-based protocol, so they offer excellent performance benefits. This will not work out of the box with most hosting platforms. In fact, you can't even call the service directly from a web browser. In this example, we'll consume the gRPC service with a special web adapter. This allows you to create the client stubs and use them in browser-based JavaScript. This example uses the [gRPC](https://grpc.io) libraries for the main gRPC service and [grpcwebproxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy) for browser support. ## What we'll deploy Since this is an example, we'll write a quick gRPC service definition in [`hello.proto`](https://github.com/fly-examples/grpc-service/blob/master/hello.proto) that has the following methods: ```protobuf theme={null} service MainService { rpc Hello(Empty) returns (Greeting); rpc Clock(Empty) returns (stream Time); } ``` ## Deploying the gRPC service to Fly * Install [flyctl](/flyctl/install) * Login to Fly using `fly auth login` * Create and deploy the app with `fly launch` ## Using the gRPC service With gRPC, you'll usually use the generated client libraries in the language of your choice. To make sure our service is working, we'll use the [`grpcurl`](https://github.com/fullstorydev/grpcurl) tool: ```shell theme={null} grpcurl -proto hello.proto grpc-test.fly.dev:443 MainService/Hello ``` You can also test streaming by calling the `Clock` method. It sends a timestamp every second: ```shell theme={null} grpcurl -proto hello.proto grpc-test.fly.dev:443 MainService/Clock ``` ## What's Happening Inside The gRPC servers that we're running have one special feature that makes them difficult to deploy on many systems – they use the relatively new HTTP/2 protocol with a special feature called HTTP Trailers. Most HTTP/2 load balancers will terminate the HTTP/2 connection at the load balancer, and then make a more compliant HTTP/1.1 connection on the backend to the application server. This won't work with gRPC. Luckily, Fly supports connecting to your service using HTTP/2, with the following configuration in your `fly.toml`: ```toml theme={null} [http_service] internal_port = 54321 force_https = true auto_stop_machines = 'stop' auto_start_machines = true min_machines_running = 0 processes = ['app'] [http_service.http_options] h2_backend = true [http_service.tls_options] alpn = ["h2"] ``` Once the TCP request reaches your gRPC application, the server will then take over and provide threading / event loop / goroutine management, depending on your chosen language, along with serialization and deserialization. ## Using the Web Proxy Because the gRPC services uses HTTP/2, you can't make requests to it from inside a browser — there are currently no browser APIs that allow direct and full control of a HTTP/2 connection. To enable use inside a browser, there's a [gRPC-Web](https://grpc.io/docs/languages/web/) specification that converts a normal HTTP/1.1 request to and from the gRPC HTTP/2 format. Multiple proxy servers that implement the spec are available, like [Envoy](https://grpc.io/docs/languages/web/basics/#configure-the-envoy-proxy) and [grpcwebproxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy) (which we've deployed here). We've generated JS code for our gRPC definition using the instructions [on the official gRPC-Web example page](https://github.com/grpc/grpc-web/tree/master/net/grpc/gateway/examples/helloworld#generate-protobuf-messages-and-client-service-stub), so let's see how to use it. In the `web-proxy` folder, the [`client.js`](https://github.com/fly-examples/grpc-service/blob/master/web-proxy/client.js) file calls the two methods of our service and writes the output into `console.log`. You can change the following [line](https://github.com/fly-examples/grpc-service/blob/master/web-proxy/client.js#L4): ```js theme={null} var client = new MainServiceClient('https://grpc-web-proxy-test.fly.dev:443'); ``` to update it with your web proxy app's hostname and then run: ```shell theme={null} npm install npx webpack client.js open index.html ``` This will re-compile the client and open up your browser, where the method repsonses are being printed into the console. You'll notice that the clock stops streaming after exactly 10 seconds – this is based on a [flag](https://github.com/improbable-eng/grpc-web/blob/b16a11b6e855a48b6bc6e369a85f3d46fcfe1a77/go/grpcwebproxy/main.go#L45) in the proxy configuration that you'll want to set if your services have longer-running methods. For example, you can set them to 1 hour by adding `--server_http_max_write_timeout=3600` and `--server_http_max_read_timeout=3600` to the `ENTRYPOINT` command in [`web-proxy/Dockerfile`](https://github.com/fly-examples/grpc-service/blob/master/web-proxy/Dockerfile#L40). ## How Does Fly Fit Into This? While gRPC works really well when communicating between servers on the same rack or same datacenter, the principles it uses apply equally well to clients on websites or mobile devices. The HTTP/2 communication channel remains open as much as possible to provide a low-latency and low-energy way to speak to your server, and the underlying [Protocol Buffers](https://developers.google.com/protocol-buffers) serialization/de-serialization system is very low-overhead, so it's great for cellular or metered-bandwidth connections. Running your backend services on Fly lets you put your services really close to every user in your global client base. If your data is in a central location, your services can use the local Redis cache that Fly provides at each edge location to cache data, and broadcast changes or invalidations via the [global Redis command broadcast](/upstash/redis). There are also data solutions from other cloud providers like [DynamoDB Global Tables](https://aws.amazon.com/dynamodb/global-tables/) (NoSQL), [Aurora Multi-Master](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database.html) (SQL) or [Google Cloud Spanner](https://cloud.google.com/spanner) (SQL) that can help spread your data across the world and closer to your application servers. The reduced latency from having your servers close to your clients is especially useful in gRPC where you want all your calls to finish as quickly as possible. If your clients are mobile applications, they'll benefit even more from the latency reduction when they're running on cellular networks, where bandwidth is low and re-connections are common. ## Testing gRPC performance with `ghz` Because gRPC applications use a special wire protocol, we can't use `cURL`, `wget` or `siege` like we would on normal servers. Instead, there are special gRPC-aware tools like [ghz](https://ghz.sh) that work great. After you enable the Fly regions you'd like to run your service in and set the container CPU & memory configuration to the size you need, you can use `ghz` to test your service: ``` ghz grpc-test.fly.dev:443 --call=MainService/Hello --proto=hello.proto ``` Keep in mind that this test will run against your closest enabled Fly region, which may or may not be the region that your users will use. gRPC testing also works a little different, because a single HTTP/2 connection is re-used across many requests. Testing against dummy methods that don't do much is really just a test of single connection bandwidth. The best way to make sure your service is running great would be to enable the instrumentation provided in your client and service libraries and exercise your service in a real-world scenario. # Deploy Livebook on Fly.io Source: https://docs.fly.io/app-guides/livebook Deploy Livebook to one Fly Machine with a 1GB volume: a ready fly.toml, LIVEBOOK_PASSWORD secret, shared-cpu-2x sizing, and auto stop and start notes. In this guide we'll deploy [Livebook](https://livebook.dev), the interactive Elixir notebook app, to a single Fly Machine using the [Fly Launch](/launch/index) approach. We'll give Livebook a 1GB [persistent volume](/volumes) on which to store notebooks and other data. ## Speedrun 1. Create a `fly.toml` file with the contents in the next section. 2. `fly launch --no-deploy` 3. `fly secrets set LIVEBOOK_PASSWORD=""` 4. `fly deploy --vm-size shared-cpu-2x --volume-initial-size 1` 5. `fly apps open` ## Prepare a Fly Launch config Fly Launch uses a TOML configuration file—called `fly.toml` by default. Prepare a directory for your Livebook app config and place a `fly.toml` file inside, with the following contents: ```toml theme={null} [build] image = "ghcr.io/livebook-dev/livebook:0.13.0" # use a prebuilt Livebook Docker image [env] ERL_AFLAGS = "-proto_dist inet6_tcp" # enable ipv6 for Fly.io private networking LIVEBOOK_DATA_PATH = "/data" # /data is the mount point of the persistent volume LIVEBOOK_HOME = "/data" LIVEBOOK_ROOT_PATH = "/data" LIVEBOOK_IP = "::" # listen on all ipv6 interfaces PORT = "8080" # listen on port 8080 [[mounts]] source = "data" # first deployment creates a volume with this name destination = "/data" # mount location on the Machine file system [http_service] internal_port = 8080 # the Livebook app listens on [::]:8080 force_https = true auto_stop_machines = true # when no http connections, the Machine stops auto_start_machines = true # Fly proxy restarts the Machine on request min_machines_running = 0 # allow all Machines to stop processes = ["app"] # the default process group ``` This is the app-wide config that gets applied to all the Machines in the app when you run `fly deploy`. ## Create a new Fly app Create a new app by running `fly launch` from the directory containing the app's `fly.toml` config file. ```bash theme={null} fly launch --no-deploy ``` Follow the prompts. Respond `Y` to copy the configuration from your existing `fly.toml` to the new app. You'll be asked for an app name and a primary region, which will be added to your local `fly.toml`. The app name must be unique across Fly.io. The primary region is the region where Machines are created for this app when not otherwise specified. ## Set a Livebook password Livebook stores your password in an environment variable. Set that variable securely using `fly secrets set`. ```bash theme={null} fly secrets set LIVEBOOK_PASSWORD="" ``` Livebook requires that this password be at least 12 characters long. Don't use this example password as a real password. ## Deploy the app Now that the app has been created and the `LIVEBOOK_PASSWORD` secret is set, you can deploy to a Machine. The `fly deploy` command creates a new release of an app using the `fly.toml` in your working directory, and rolls that out to its Machines. The first time it's run on a given app, and when it's run on an app that currently doesn't have any Machines, `fly deploy` does some extra work. For our Livebook app, it provisions public IP addresses for the HTTP service and creates the volume referred to in the `mounts` section in `fly.toml`, before creating and starting the app's Machine(s). ``` fly deploy --vm-size shared-cpu-2x --volume-initial-size 1 ``` The `fly deploy` command accepts some options to tune the initial resource provisioning. Livebook seems to want something a bit heftier than the default `shared-cpu-1x` Machine with 256MB RAM, so for the purpose of demonstration we chose a `shared-cpu-2x` size, which comes with 512MB. You can experiment with CPU and memory to see what you need for your project. We explicitly set the size of the volume to be created, although 1GB happens to be the default size for a new volume created on `fly deploy`. > [!NOTE] > > At the time of writing, you may see this error message as the first deployment finishes: > > `WARNING The app is not listening on the expected address and will not be reachable by fly-proxy.` > > If you can reach your Livebook in the browser, then you can ignore this message. Occasionally, provisioning the storage volume can delay startup of Livebook until after this check times out. ## Visit your Livebook app To use your Livebook, visit `https://.fly.dev` in the browser, or type `fly apps open`, and enter your password where prompted. Create a new notebook, and save it in the `/data` directory. You can test that your notebook was saved onto the persistent volume by running `fly apps restart` and clicking "Open" on the Livebook home page to find and open it from storage. ## A note on auto start and stop settings The `auto_stop_machines` and `auto_start_machines` config options are set to `true` in the app config. The Fly proxy will shut down the Machine when there's nobody connected to it. Livebook does try to save even "unsaved notebooks," but principle you can lose data if it shuts down and you have not saved your work. The proxy will also start the Machine when a connection is requested. This means bots may wake up your app. When you visit the app when it's stopped, it will take a moment before it's running again. If you'd rather start and stop your Livebook app manually, you can do so directly with flyctl. Set `auto_stop_machines` and `auto_start_machines` both to `false`, and redeploy. Use the `fly machine stop` and `fly machine start` commands to stop the Machine when you're not using it and to start it up again when you're ready. ## A note on persistent storage Fly volumes don't automatically synchronize across an app, so the simplest way to run Livebook with volume storage is as a single Machine, with the availability repercussions that implies. Other deployment configurations are beyond the scope of this guide, but Livebook does provide the option to connect with an object storage service. # Deploy MinIO Object Storage to Fly.io Source: https://docs.fly.io/app-guides/minio Self-host S3-compatible MinIO object storage on Fly.io with a volume, reach the console via fly proxy, use the mc client, and set up a 3-node cluster. Object storage is useful when your apps need access to unstructured data like images, videos, or documents. Amazon's S3 is an obvious solution, but you can also host your own S3-compatible object storage using the AGPLv3-licensed [MinIO](https://min.io/). We're going to set up a single instance of MinIO on Fly.io, backed by [Fly Volume storage](/volumes). A note: it's been observed that some features of the MinIO browser console are memory-intensive enough to crash a VM with 256MB of RAM. For even small-scale production use you'll probably need a larger (non-free) VM. 512MB seems to work for poking around and uploading small objects. (For perspective, [MinIO docs recommend a minimum of 128GB RAM](https://min.io/docs/minio/container/operations/checklists/hardware.html#minio-hardware-checklist) for deployment at scale.) This is a guide to a demonstration deployment of the official standalone MinIO Docker image, and isn't tuned in any way. Check out the [MinIO docs](https://min.io/docs/minio/linux/index.html) for more sophisticated use. ## Dockerfile We'll use the official [minio/minio](https://hub.docker.com/r/minio/minio) Docker image, but with a custom start command. Here's the Dockerfile to build that: ```docker theme={null} FROM minio/minio CMD [ "server", "/data", "--console-address", ":9001"] ``` This just takes the MinIO image and runs it with the command `server /data --console-address :9001`, so that the server runs using the `/data` directory for storage. We'll get to that directory in a moment. The `--console-address` flag specifies which port to run the web-based admin console on. Otherwise, MinIO chooses a random port. See below for how to access this panel after MinIO has been deployed. ## Initializing the app Initialize the app with `fly launch`. Here we use flags to specify an app name and the organization it belongs to. The most important flag is `--no-deploy`, because we still have Things to Do before we deploy: ```bash cmd theme={null} fly launch --name my-minio --org personal --no-deploy ``` ```out out theme={null} Creating app in /Users/jfent/fly-tests/my-minio Scanning source code Detected a Dockerfile app Some regions require a paid plan (bom, fra, maa). See https://fly.io/plans to set up a plan. ? Choose a region for deployment: London, United Kingdom (lhr) App will use 'lhr' region as primary Created app 'my-minio' in organization 'personal' Admin URL: https://fly.io/apps/my-minio Hostname: my-minio.fly.dev Wrote config file fly.toml Validating /Users/jfent/fly-tests/my-minio/fly.toml Platform: machines ✓ Configuration is valid Your app is ready! Deploy with `flyctl deploy` ``` (Hold your horses. Your app is not, in fact, ready.) ## (Un)configure networking There's no need for this app to be accessible from the public internet, if you run your object storage within the same [Fly IPv6 private network](/networking/private-networking) as the app(s) that want to connect to it. Delete the `[http_service]` block in `fly.toml`. Now the Fly proxy won't pass any requests to the app from outside your private network. ## Disk storage The application's VM and image are ephemeral. When the app is stopped or moved it loses any data written to its file system. For persistent storage, provision a volume with a name and a size in the same region as the app. ```bash cmd theme={null} fly vol create miniodata --region lhr ``` ```out out theme={null} Warning! Every volume is pinned to a specific physical host. You should create two or more volumes per application to avoid downtime. Learn more at https://fly.io/docs/volumes/ ? Do you still want to use the volumes feature? Yes ID: vol_6vjd0dkm5ny038mv Name: miniodata App: my-minio Region: lhr Zone: cbfa Size GB: 3 Encrypted: true Created at: 13 Nov 23 12:07 UTC Snapshot retention: 5 ``` We didn't specify a size, so we got the default: 3GB. Tell the app to mount this volume onto its `/data` directory, by appending to `fly.toml`: ```toml theme={null} [mounts] source = "miniodata" destination = "/data" ``` ## Secrets MinIO uses environment variables `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` to store administrator login information. Instead of using normal environment variables, use `fly secrets set` to pass these sensitive values to the server in encrypted form. They'll only be decrypted at runtime. ```bash theme={null} fly secrets set MINIO_ROOT_USER= MINIO_ROOT_PASSWORD= ``` ## Deployment Now the app is, in fact, ready to deploy: ```bash theme={null} fly deploy ``` ## Accessing the web-based MinIO admin panel MinIO has a web interface. It's served on the port specified by `--console-address` in the Dockerfile, which we set to `9001`. We can access this panel over our private WireGuard network. One way to do this is to set up a [regular WireGuard tunnel](/networking/private-networking), and visit `http://my-minio.internal:9001` in the browser. A simpler way is to use flyctl's user-mode WireGuard to proxy a local port to the app: ```bash cmd theme={null} fly proxy 9001 ``` ```out out theme={null} Proxying local port 9001 to remote [my-minio.internal]:9001 ``` Leave this running and visit `localhost:9001` with the browser. Log into the admin panel with the `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` values set using Fly Secrets above, and you can create buckets, do administration, and upload and download files right from the browser. ## Using the `mc` MinIO Client You can connect to your MinIO with the `mc` command-line [MinIO Client](https://min.io/docs/minio/linux/reference/minio-mc.html). MinIO listens for non-browser connections on port 9000, by default. If you're connecting using `fly proxy`, you'll have to proxy port 9000 to use `mc`. You can set up an alias to connect more conveniently to your MinIO. If you're using `fly proxy`: ```bash theme={null} mc alias set proxy-to-minio http://localhost:9000 ``` If you're hooked up with WireGuard: ```bash theme={null} mc alias set miniotest http://my-minio.internal:9000 ``` Test the alias by checking your MinIO's status: ```bash theme={null} mc admin info miniotest ``` Create a new non-admin user with `readwrite` permissions. This user won't have full admin privileges, but will be able to create and save files to buckets. ```bash theme={null} mc admin user add miniotest ``` At this point `` can log in and read the contents of your buckets. Make that read-write access: ```bash theme={null} mc admin policy set miniotest readwrite user= ``` ## Multi-node deployment What if you want high availability? You can deploy MinIO in a multi-node configuration! Each node needs to know where to find the other nodes in the configuration. This can be done using either hostnames or IP addresses, but MinIO requires that they're sequential. We'll use hostnames since we can modify `/etc/hosts` to ensure their "sequential-ness". Ensuring sequential IP addresses is much more tricky! There's going to be a bit of back-and-forth here since we don't have any private IPs to put into `/etc/hosts` until we create the Machines, but we need a Dockerfile to create them in the first place. Bear with me! ### Dockerfile We need to change the `CMD` in the Dockerfile to declare that we intend to run a multi-node deployment: ```docker theme={null} FROM minio/minio ENV MINIO_SERVER_URL="http://minio1.local:9000" CMD [ "server", "http://minio{1...3}.local:9000/data", "--console-address", ":9001"] ``` We're using [expansion notation](https://min.io/docs/minio/linux/operations/install-deploy-manage/deploy-minio-multi-node-multi-drive.html#sequential-hostnames) to tell MinIO that there will be 3 nodes in the cluster. ### Disk Storage Create 3 volumes, one for each node: ```bash cmd theme={null} fly vol create miniodata --count 3 --region lhr ``` ```output output theme={null} ID: vol_8r6kj28xq7e7qw14 Name: miniodata App: my-minio Region: lhr Zone: 81b8 Size GB: 3 Encrypted: true Created at: 13 Nov 23 12:38 UTC Snapshot retention: 5 ID: vol_9vle38y30eog2p8r Name: miniodata App: my-minio Region: lhr Zone: d88f Size GB: 3 Encrypted: true Created at: 13 Nov 23 12:38 UTC Snapshot retention: 5 ID: vol_7r11e3jk1p8g17pr Name: miniodata App: my-minio Region: lhr Zone: cbfa Size GB: 3 Encrypted: true Created at: 13 Nov 23 12:38 UTC Snapshot retention: 5 ``` ### Initial deployment You need to deploy and scale the app in order for private IPs to be assigned. First, deploy: ```bash theme={null} fly deploy ``` ...then scale up to 3 Machines: ```bash theme={null} fly scale count 3 ``` ### Private networking One of the great things about Fly.io is the [private networking](/networking/private-networking) we get for free. We'll use our Machines' private IPs to define hostnames in `/etc/hosts` which our nodes will use to communicate with each other. Get the private IPs of your Machines: ```bash cmd theme={null} fly ip private ls ``` ```out out theme={null} ID REGION IP 4d89703f427258 lhr fdaa:3:7c6d:a7b:8f:143c:79a5:2 080e475b1d2758 lhr fdaa:3:7c6d:a7b:172:ac21:8674:2 683d479cde4368 lhr fdaa:3:7c6d:a7b:172:21da:b0bc:2 ``` Ideally we'd update `/etc/hosts` directly in the Dockerfile, but Docker won't let us do this at build time. Instead, we'll create a script called `run.sh` that will add our host definitions at runtime: ```sh theme={null} #!/bin/sh cat <> /etc/hosts fdaa:3:7c6d:a7b:8f:143c:79a5:2 minio1.local fdaa:3:7c6d:a7b:172:ac21:8674:2 minio2.local fdaa:3:7c6d:a7b:172:21da:b0bc:2 minio3.local EOF # This script comes with the minio/minio image /usr/bin/docker-entrypoint.sh server http://minio{1...3}.local:9000/data --console-address :9001 ``` ### Final deployment Now update your Dockerfile to execute `run.sh`: ```docker theme={null} FROM minio/minio ENV MINIO_SERVER_URL="http://minio1.local:9000" COPY run.sh run.sh ENTRYPOINT [ "/bin/sh" ] CMD [ "run.sh" ] ``` When we `fly deploy`, we should have a working multi-node MinIO deployment! If you want to see it in action, try proxying to two different Machines. In one terminal: ```bash cmd theme={null} fly proxy 9001:9001 -s ``` ```out out theme={null} ? Select instance: lhr.my-minio.internal (fdaa:3:7c6d:a7b:172:21da:b0bc:2) Proxying local port 9001 to remote [fdaa:3:7c6d:a7b:172:21da:b0bc:2]:9001 ``` ...and in a different terminal: ```bash cmd theme={null} fly proxy 9002:9001 -s ``` ```out out theme={null} ? Select instance: lhr.my-minio.internal (fdaa:3:7c6d:a7b:172:ac21:8674:2) Proxying local port 9002 to remote [fdaa:3:7c6d:a7b:172:ac21:8674:2]:9001 ``` Visit `http://localhost:9001` and `http://localhost:9002` in separate browser tabs, and log in to both with your `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD`. Create a bucket in one tab and then refresh the other tab. If everything is working, you should see the bucket in both! ## What's next? This was a basic guide for getting an instance of MinIO deployed on Fly.io, scratching the surface of what you might want to do with an S3-compatible object store. You can use your MinIO bucket storage right from the web interface. Your apps can talk to it from within the same private network. [MinIO docs](https://min.io/docs/minio/linux/index.html) cover more advanced usage. # Appkata: MQTT - Event driven messaging Source: https://docs.fly.io/app-guides/mqtt > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Appkata-mqtt Run your own MQTT server for event-based messaging on Fly [Read this guide on Fly.](/app-guides/mqtt) ## Rationale Your modern application is more reactive than ever and with an event-based messaging platform like MQTT, you can communicate and react more than ever. MQTT allows for publish and subscribe messages to be sent to topics and broadcast to listening clients with options to persist data and enforce quality of service on connections. We'll be using Mosquitto, a popular free MQTT broker, to create a persistent messaging hub on Fly. ## Preparing You will need [Mosquitto](https://github.com/eclipse/mosquitto) installed locally as we'll be using some of its tools in the process of creating and testing our server. The other thing we'll need is [mkcert](https://github.com/FiloSottile/mkcert), which allows you to create your own certificate authority and certificates. If you are in macOS, install [Homebrew](https://brew.sh/) and then run `brew install mosquitto mkcert`. ## Initialising We're ready to begin building on the official Mosquitto image, eclipse-mosquitto and making some changes to it with a Dockerfile: ``` FROM eclipse-mosquitto:openssl RUN mkdir /etc/mosquitto ADD mosquitto.conf /etc/mosquitto ADD passwd /etc/mosquitto ADD certs /etc/mosquitto/certs CMD ["/usr/sbin/mosquitto", "-c", "/etc/mosquitto/mosquitto.conf"] ``` We select the openssl build of Mosquitto and then add a mosquitto config directory to it in `/etc/mosquitto`. We then copy over a configuration file, passwords, and certificates to the image. We wrap up with a startup command which runs Mosquitto using our configuration. Now we need to create a slot for the application on Fly. Run ```bash cmd theme={null} fly init appkata-mqtt --dockerfile --port 1883 --org personal ``` ```out out theme={null} Selected App Name: appkata-mqtt New app created Name = appkata-mqtt Organization = personal Version = 0 Status = Hostname = App will initially deploy to lhr (London, United Kingdom) region Wrote config file fly.toml ``` And it will create a Fly app called `appkata-mqtt` (though you'll have to find another name because fly app names are unique - if you are having trouble making one miss out the app name and Fly will generate one for you). It will also create a `fly.toml` file which will use the Dockerfile to build the app. There are to the most important things to know are the app name and the region where the app will deploy - in this case, lhr. All that's missing now are all those files. Let's go fill in the gaps. ## mosquitto.conf This file sets up the server: ``` persistence true persistence_file mosquitto.db persistence_location /mosquitto/data/ allow_anonymous false password_file /etc/mosquitto/passwd cafile /etc/mosquitto/certs/rootCA.pem certfile /etc/mosquitto/certs/mqtt-server.crt keyfile /etc/mosquitto/certs/mqtt-server.key require_certificate false tls_version tlsv1.2 ``` A quick run through this file is in order. First, we set up where Mosquitto will persist data; specifically in a file called `/mosquitto/data/mosquitto.db`. Then we are on to password configuration: we require them and the password file will be in `/etc/mosquitto/passwd`. Third, we set up the TLS/SSL parameters. Mostly the locations of files we will create soon, and some settings, like `require_certificate` set to false to turn off client authentication and a reasonable selection of TLS version for communications. ## Passwords Let's create that password file. This is simply a matter of coming up with a username and password and then running ```bash theme={null} mosquitto_passwd -c ./passwd username ``` It'll prompt you for a password (twice) and generate out password file. If you want more users and passwords, check out the mosquitto\_passwd docs where it covers batch modes and converting plaintext passwords into hashed passwords. Now we have that in place, that just leaves the certificates. ## Certificates This is where we use `mkcert`. In one command, we'll ask it to generate ```bash theme={null} mkcert -key-file certs/mqtt-server.key -cert-file certs/mqtt-server.crt appkata-mqtt.fly.dev ``` This will create a set of certificates for our server at `appkata-mqtt.fly.dev` - remember to replace the hostname with your app name followed by .fly.dev to make your own certificates. As mkcert generates its own CA root, to use these certificates, we need a copy of that root. ```bash theme={null} cp "$(mkcert -CAROOT)/rootCA.pem" certs/rootCA.pem ``` This set of three files will allow the server to use TLS. Incoming clients will need the certs/rootCA.pem file to work with this server, so make sure you have a copy. ## Services and Volumes With the Mosquitto configuration done, we now need to prepare the Fly configuration. The first part of this is creating the volume to store the persistent data. We've already mentioned the directory name in `mosquitto.conf` and the persistence location setting. Now we need to create a Fly disk volume to hold that data in the region where the app will deploy. We got that information when we initialized the app at the start. We'll make a `mosquitto_data` volume. ```bash theme={null} fly volumes create mosquitto_data --region lhr ``` As it stands, that volume is not connected to our application. That can be set up in the `fly.toml` file. Open it and add these lines: ```toml theme={null} [[mounts]] source="mosquitto_data" destination="/mosquitto/data" ``` This will mount the volume at `/mosquitto/data`. Before you close, there is something else to do in `fly.toml` and thats configure the services. By default, the `fly.toml` file directs traffic on port 80 and port 443 to the internal port like so: ``` [[services.ports]] handlers = ["http"] port = "80" [[services.ports]] handlers = ["tls", "http"] port = "443" ``` Well, we aren't going to set a handler for MQTT traffic and we're going to use port 10000 for it. So replace those two sections of config with: ```toml theme={null} [[services.ports]] handlers = [ ] port = "10000" ``` Save the file and we're ready to deploy. ## Deploying and Testing To deploy our server is simple: ```bash theme={null} fly deploy ``` Once deployed, we'll want to test it. Open up two terminals. In one terminal we'll set up a subscribe to listen to all topics: ```bash theme={null} mosquitto_sub -L mqtts://username:password@appkata-mqtt.fly.dev:10000/# --cafile certs/rootCA.pem ``` Insert the username and passwords you used when creating the passwd file with mosquitto\_passwd. Now go to the other terminal and run: ```bash theme={null} mosquitto_pub -L mqtts://username:password@appkata-mqtt.fly.dev:10000/tests --cafile certs/rootCA.pem -l ``` Now type something and hit return and it should show up in the sub terminal. You now have a working, TLS encrypted, username/password authorized Mosquitto server, ready to rebroadcast your message traffic. ## Discuss * You can discuss this example on its dedicated [community.fly.io](https://community.fly.io/t/appkata-mqtt/390) topic. # Multiple processes inside a Fly.io app Source: https://docs.fly.io/app-guides/multiple-processes Run multiple processes in a Fly.io app using process groups in fly.toml, a Bash script, supervisord, or overmind with a Procfile, or use separate apps. > [!NOTE] > > This guide discusses different ways to run multiple processes in your app. To learn about process groups in Fly Apps and the `[processes]` configuration in `fly.toml`, see [Run multiple process groups in an app](/launch/processes). For process group configuration with the Machines API, see the `config.processes` object in the [Machine config](/machines/api/machines-resource#machine-config-object-properties). This comes up a lot: how can you run multiple programs in an app on Fly.io? Recall that Fly.io apps are shipped to us in OCI images, usually built by Docker, and Docker has… opinions… about running multiple things in a container. Well, [we don't use Docker to run images](https://fly.io/blog/docker-without-docker/). Your app is running in a [Machine](/machines), a fast-launching VM with its own kernel. You can do pretty much anything you want inside of it, including running as many programs as you like. Most of the time, the trick is just telling Docker how to do that. There are a couple different ways to run multiple processes in a Fly.io app. All of them address the first rule of programs running in a Fly Machine: when your entrypoint program exits, our `init` kills the Machine and we start a new one. So ultimately, *something* has to keep running "in the foreground". ### Setting the scene Let's use the world's dumbest Golang app as our test case. Here's a Dockerfile: ```Dockerfile theme={null} FROM golang RUN apt-get update && \ apt-get install -y \ bash \ curl RUN mkdir /app WORKDIR /app ADD go.* *.go /app/ RUN go build CMD tail -f /dev/null ``` I'll spare you the 10 lines of Go code, but it's just a server that runs "Hello Foo" on 8080/tcp if you run it without arguments, and "Hello Bar" on 8081/tcp if you run it `-bar`. Use your imagination! And we're not running anything right now; it's just going to sit there inert while we figure out how to run both instances of the program. With a working Dockerfile, we can spin this up with `fly launch`. We'll edit `fly.toml` to tell Fly.io about the two internal ports we're listening on, and `fly deploy` to effect that change. Now, some options to actually run this stuff: ### Process groups [Process groups](/launch/processes) are the recommended way to run multiple processes. This method runs each process in its own Machine or group of Machines. You can define multiple process groups in your `fly.toml`, giving each a name. Each defined process runs in its own Machine or group of Machines within the one app. You can see that in action in the below (truncated) `fly.toml` file: ```toml theme={null} [processes] web = "/app/server" bar_web = "/app/server -bar" [[services]] processes = ["web"] # this service only applies to the web process http_checks = [] internal_port = 8080 protocol = "tcp" script_checks = [] ``` Here we define two processes: `web` and `bar_web`. The command defined for each process group is setting the *command* passed to your Dockerfile *entrypoint*. That means your entrypoint needs to be able to handle a command being passed to it! Here's an example of such an entrypoint: ```bash theme={null} #!/bin/bash if [[ $# -gt 0 ]]; then # If we pass a command, run it exec "$@" else # Else default to starting the server exec /app/server fi ``` Note that under the `[[services]]` section of the `fly.toml` file, we define a service for process `web`. The `processes = [...]` array acts as a filter - it will apply only to the processes listed. You can also scale Machines horizontally and vertically by process group. See the [process groups](/launch/processes) docs for details. ### Just use Bash This is gross, but a suggestion Docker makes in its own documentation, so it must be OK. We boot our Docker container into a shell script. That shell script is: ```bash theme={null} #!/bin/bash set -m # to make job control work /app/server & /app/server -bar & fg %1 # gross! ``` The trick here is we can run any number of programs in the background, as long as we foreground one of them at the end of the script, so the script itself doesn't exit (and tell us to kill the VM). Call this `gross.sh` and change the Dockerfile entrypoint: ```dockerfile theme={null} ADD gross.sh /app/ CMD /app/gross.sh ``` This works well enough to connect the app to Fly.io's Anycast network, so here's where we'd crack open `fly.toml`: ```toml theme={null} [[services]] internal_port = 8080 protocol = "tcp" [[services.ports]] handlers = ["http"] port = "80" [[services.ports]] handlers = ["tls", "http"] port = "443" ``` (It's a contrived example, so I'm only lighting one of the ports up, but whatever). ### Use Supervisord The other Docker documentation suggestion: use `supervisor`. `supervisor` is an actual process manager; it'll run in the foreground and manage all our processes and, most importantly, when our processes exit, `supervisor` will (configurably) restart it. `supervisor` has [a lot of configuration options](http://supervisord.org/configuration.html). But because we're running on Fly.io, we mostly don't care about them; the platform is doing a lot of this work for us. So the `supervisor` configuration we want is pretty simple: ```toml theme={null} [supervisord] logfile=/dev/stdout logfile_maxbytes=0 loglevel=info pidfile=/tmp/supervisord.pid nodaemon=true user=root [unix_http_server] file=/tmp/supervisor.sock [program:foo] command=/app/server [program:bar] command=/app/server -bar ``` If you're running an app on Fly.io, chances are you don't want it composting its own logs in `/var/log`, so we just configure `supervisor` to log to `stdout` like a normal person. The rest, apart from our command lines, is just boilerplate. (You could drop privileges by changing the `user` line if you wanted; whether you want to depends on whether there's meaningful security to get by running multi-user in your particular container. If basically everything in the container shares the same data, there probably isn't.) Our Dockerfile derives from the `golang` base image, and to add `supervisor` to it, we just need to add the dep: ```Dockerfile theme={null} FROM golang RUN apt-get update && \ apt-get install -y \ bash \ supervisor \ curl # ... ADD supervisor.conf /app/ CMD supervisord -c /app/supervisor.conf ``` ### Use a Procfile Manager `supervisor` works, and lots of people use it. It's a bit fussy, though, and it pulls Python in as a dependency. There are lots of other process managers. One useful genus of them are the `Procfile` managers. `Procfiles` are so dirt simple you don't need me to explain them; just look: ``` foo: /app/server bar: /app/server -bar ``` A Procfile manager I like a lot is [overmind](https://github.com/DarthSim/overmind). `overmind` is a Go program, so it's got a small runtime, and you could, if you were fussy, build a container that just takes the `overmind` binary and none of its build deps. We won't bother, though, since we're already bringing those deps in. So: ```Dockerfile theme={null} FROM golang RUN apt-get update && \ apt-get install -y \ bash \ tmux \ curl # ... RUN GO111MODULE=on go get -u github.com/DarthSim/overmind/v2 ADD Procfile /app/ CMD ["overmind", "start"] ``` `overmind` has a bunch of interesting features, most notable among them that it wraps `tmux`, so you can get a terminal window on any of your running programs. Deploy, and then you can: ```bash theme={null} $ flyctl ssh console root@30952b25:/# cd /app root@30952b25:/# overmind connect foo # (clouds part, screen fills with foo's terminal) ``` If you're not a `tmux` person, `ctrl-b d` detaches you from that window. ### There are so many other process managers Use whichever you like! The only limitation you're going to run into is that Fly.io owns your `init` (sorry!); we have to be PID 1. There are a bunch of process managers that want to replace `init`, and those'll be tricky to get to work in a Fly.io app. ### Maybe you want multiple apps, though There are good reasons to run multiple programs in a single container, but sometimes when you're doing that you're actually working against the platform (which is why Docker keeps telling you not to do that). If you're running multiple heavy-weight things in a single container, you might explore just running them as separate Fly.io apps. The advantage to doing this, apart from the apps not fighting over the same CPUs and memory, is that you can scale separate apps independently, and put them in different sets of regions. Fly.io apps can talk to each other over a [private network](/networking/private-networking) that's always available. They can find each other under the `.internal` top-level domain (if your apps are `foo` and `bar`, they'll be in the DNS as `foo.internal` and `bar.internal`). Because the network connection is private and encrypted, you can generally just talk back and forth without extra authentication until you know you need it; in other words, you can keep things simple. The main conveniences you give up when splitting into separate apps are that secrets are shared across all process groups in a single app, and all process groups deploy from the same Docker image. There are also cases where separate apps are the only option. For instance, the [metrics-based autoscaler](/launch/autoscale-by-metric) operates on a whole app, so workloads that need independent scaling behavior have to live in their own apps. You can still share a single built image across several apps. Build it once without deploying: ```bash theme={null} fly deploy --build-only --push ``` This outputs a tag of the form `registry.fly.io/app:deployment-xxxx`. Any app in the same organization can then deploy from that image: ```bash theme={null} fly deploy --image registry.fly.io/app:deployment-xxxx ``` The main thing to watch out for is that secrets have to be set on each app individually, which can mean some duplication. ### Maybe you don't need multiple processes There are a bunch of reasons people want to run multiple things in a container that Fly.io already takes care of for you. For instance: [metrics are a built-in feature of the platform](https://fly.io/blog/hooking-up-fly-metrics/), as are logs. You might not need to run helper processes for this kind of stuff. Another thing people run multiple programs to get is static asset HTTP service; you'll run your app server, and a small webserver alongside it to serve assets. You don't need to do this if you don't want! Instead, you can ask the Fly.io platform to do it for you, by adding `statics` directives to your `fly.toml`: ```toml theme={null} [[statics]] guest_path = "/app/public" url_prefix = "/public" ``` When Fly.io sees that in your `fly.toml`, it'll fish your static assets out of your container and serve them directly from our proxies at the `url_prefix` you specify. This is faster and, of course, doesn't demand you package an HTTP server into your container. # Use a MySQL Database Source: https://docs.fly.io/app-guides/mysql-on-fly Run self-managed MySQL on Fly.io from the official image: volume, secrets, [processes] datadir and auth plugin flags per version, fly proxy, and backups. If you just want to run a quick, self-managed MySQL instance on Fly.io, here's how to do it. It's pretty basic, with one caveat around using a [Fly Volume](/volumes) to persist data. Most Fly apps use a `Dockerfile` to define an application and its dependencies. However, in this case we can use MySQL's official container directly - no need for a custom `Dockerfile`! Here's how to run MySQL. ## Create the App We'll run MySQL as a new app: ```bash theme={null} # Make a directory for the mysql app mkdir my-mysql 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.0.37 ``` Type `y` when prompted to tweak the default app settings. Then, on the Fly Launch page: * Name the app whatever you'd like. The name will become a hostname our application uses to connect to the database, such as `my-mysql.internal`. * If you're using MySQL 8, it's a good idea to add some additional RAM to the VM: we recommend selecting 2GB of VM Memory. Confirm the settings and return to your terminal. ## Configure the App Let's create a volume straight-away. If you don't create a volume, you'll lose all of your data on each deployment. ```bash theme={null} # Create a volume named "mysqldata" within our app "my-mysql" fly volumes create mysqldata --size 10 # gb ``` We also need to set some secrets required by the [MySQL container](https://hub.docker.com/_/mysql): ```bash theme={null} # Set secrets: # MYSQL_PASSWORD - password set for user $MYSQL_USER # MYSQL_ROOT_PASSWORD - password set for user "root" fly secrets set MYSQL_PASSWORD=password MYSQL_ROOT_PASSWORD=password ``` Save these secrets somewhere, because they're not accessible after you set them. Finally, edit the `fly.toml` file that `fly launch` generated. ```toml theme={null} # Keep your own app name app = 'my-mysql' # Keep your own primary region primary_region = "bos" [build] image = 'mysql:8' [[vm]] cpu_kind = 'shared' cpus = 1 memory_mb = 2048 # The [processes] section is different for 8+, 8.4, and 5.7. Use the one that matches your version. # Use the following for versions 8 to 8.3: [processes] app = """--datadir /data/mysql \ --default-authentication-plugin mysql_native_password""" # Uncomment and use the following for 8.4: # [processes] # app = """--datadir /data/mysql \ # --mysql-native-password=ON""" # Uncomment and use the following for 5.7: # [processes] # app = "--datadir /data/mysql" # Add the following sections for all versions [mounts] source = "mysqldata" destination = "/data" [env] MYSQL_DATABASE = "some_db" MYSQL_USER = "non_root_user" ``` There are a few important things to note: 1. We deleted the `[[http_service]]` block and everything under it. We don't need it! 2. In the `[env]` section, we added two not-so-secret environment variables that MySQL will need to initialize itself. * The `MYSQL_USER` here should be any user but `root`, which already exists. 3. 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.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. > [!NOTE] > > **Important**: Set MySQL's data directory to a subdirectory of your mounted volume. Mounting a disk in Linux usually results in a `lost+found` directory being created. However, MySQL won't initialize into a data directory unless it's completely empty. That's why you need to use a subdirectory of the mounted location: `/data/mysql`. ## Deploy the App We're now ready to deploy the MySQL app! Go ahead and run: ```bash theme={null} fly deploy ``` After a minute of MySQL initializing itself (on its first run), you should now have an app running MySQL! Your other apps can access the MySQL service by its name. In my case, I would use `my-mysql.internal` as the hostname. Any app that needs to access the database should set the hostname and username as environment variables, and create a secret for the database password. ## Debugging Possible Errors ### `mysqld: Table 'mysql.plugin' doesn't exist` This error is an indicator that the [mysql System Schema](https://dev.mysql.com/doc/refman/8.4/en/system-schema.html) inside the [MySQL data directory](https://dev.mysql.com/doc/refman/8.4/en/data-directory.html) is empty. This can happen due to different reasons. One of them is when a MySQL app is previously deployed with `--default-authentication-plugin`, and it gets re-deployed with the updated syntax `--mysql-native-password=ON`. The quickest way to fix this is to revise the `--datadir` flag to a different directory from what was previously set: ```toml theme={null} # Please revise the MySQL data directory to another location # For example if you had: [processes] app = "--datadir /data/mysql" # You can change the path to a different directory [processes] app = "--datadir /data/mysql_ ``` Changing the [MySQL data directory](https://dev.mysql.com/doc/refman/8.4/en/data-directory.html) will allow MySQL to completely initialize the new directory with a complete `mysql system schema.` Once done, redeploy your changes with `fly deploy`. ## Access the database from outside To connect to your MySQL database from outside of your Fly organization, you need a WireGuard connection. However, `fly` on your local machine can connect using [user-mode WireGuard](https://fly.io/blog/our-user-mode-wireguard-year/) magic, without you having to set up your own WireGuard tunnel. You can forward the MySQL server port to your local machine using [`fly proxy`](/flyctl/cmd/fly_proxy): ```bash theme={null} flyctl proxy 3306 -a my-mysql ``` You can also set a different local port, if your `3306` port is already in use: ```bash theme={null} flyctl proxy 13306:3306 -a my-mysql ``` Then connect to your MySQL server at `localhost:3306` and the username and password credentials from above: ```bash theme={null} mysql --protocol=tcp -h localhost -P 3306 -u non_root_user -ppassword some_db ``` ## Backups We'll take a snapshot of the created volume every day. We retain 5 days of snapshots. To restore a snapshot, make sure you have the latest version of the flyctl, and then create a new volume using the `--snapshot-id` flag. ```bash theme={null} # Get a volume ID fly volumes list -a my-mysql # List snapshots for a volume fly volumes snapshots list vol_xxx # Create a new volume from a snapshot fly volumes create --snapshot-id vs_xxx --size 10 ``` # Global NATS Cluster Source: https://docs.fly.io/app-guides/nats-cluster > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Global NATS Cluster [NATS](https://docs.nats.io/) is an open source messaging backend suited to many use cases and deployment scenarios. We use it for internal communications at Fly. This repo shows how to use it for your application. This example creates a federated mesh of NATS servers that communicate over the private, encrypted IpV6 network available to all Fly organizations. ## Setup 1. `fly launch --no-deploy` > You'll be prompted for an app name. Hit return to let Fly generate an app name for you. Pick your target organizatio and a starting region. 2. `flyctl deploy` > This will start NATS with a single node in your selected region. 3. Add more regions with `flyctl regions add ` or > For this demo, we set `ord`, `syd`, `cdg` regions. ```bash theme={null} fly regions set ord syd cdg ``` 4. Scale the application so it can place nodes in the regions. ```bash theme={null} fly scale count 3 ``` Then run `flyctl logs` and you'll see the virtual machines discover each other. ``` 2020-11-17T17:31:07.664Z d1152f01 ord [info] [493] 2020/11/17 17:31:07.646272 [INF] [fdaa:0:1:a7b:abc:21de:af5f:2]:4248 - rid:1 - Route connection created 2020-11-17T17:31:07.713Z 21deaf5f cdg [info] [553] 2020/11/17 17:31:07.704807 [INF] [fdaa:0:1:a7b:81:d115:2f01:2]:34902 - rid:19 - Route connection created 2020-11-17T17:31:08.123Z 82fabc30 syd [info] [553] 2020/11/17 17:31:08.114852 [INF] [fdaa:0:1:a7b:81:d115:2f01:2]:4248 - rid:7 - Route connection created 2020-11-17T17:31:08.259Z d1152f01 ord [info] [493] 2020/11/17 17:31:08.241644 [INF] [fdaa:0:1:a7b:b92:82fa:bc30:2]:45684 - rid:2 - Route connection created ``` ## Testing the cluster While the cluster is only accessible from inside the Fly network, you can use Fly's [Wireguard support](/networking/private-networking) to create a VPN into your Fly organization and private network. Then you can use tools such as [natscli](https://github.com/nats-io/natscli) to subscribe to topics, publish messages to topics and perform various tests on your NATS cluster. Install the tool first. Once installed, create a context that points at your NATS cluster: ```bash theme={null} nats context add fly.demo --server appname.internal:4222 --description "My Cluster" --select ``` You can subscribe to a topic with `nats sub topicname`: ```bash theme={null} nats sub fly.demo ``` And then, in another terminal sessions, we can use `nats pub topicname` to send either simple messages to that topic: ```bash theme={null} nats pub fly.demo "Hello World" ``` Or send multiple messages: ```bash theme={null} nats pub fly.demo "fly.demo says {{.Cnt}} @ {{.TimeStamp}}" --count=10 ``` You're ready to start integrating NATS messaging into your other Fly applications. ## What to try next 1. [NATS streaming](https://docs.nats.io/nats-streaming-concepts/intro) offers persistence features, you can create a NATS streaming app by modifying this demo and adding volumes: `flyctl volume create` 2. Create a [NATS super cluster](https://docs.nats.io/nats-server/configuration/gateways) let you join multiple NATS clusters with gateways. If you want to run regional clusters, you can query the Fly DNS service to with `..internal` to find server in specific regions. ## Discuss You can discuss this example (and the paired 6pn-demo-chat example) on the [dedicated Fly Community topic](https://community.fly.io/t/new-examples-nats-cluster-and-6pn-demo-chat/562). # Appkata: Node-RED - Flow Programming Source: https://docs.fly.io/app-guides/node-red > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Appkata-Node-RED Bringing Node-RED to your Fly applications [Read this guide on Fly.](/app-guides/node-red) ## Rationale If you are looking for a quick way to interconnect your various applications then [Node-RED](https://nodered.org/) may be the low code option you've been looking for. Node-RED began its life as a way of putting together Internet Of Things applications, modeling systems as a flow of events which entered through an input node, passed through processing nodes, and eventually arrived at an output node or nodes. For example, an MQTT event could arrive through an MQTT node, be passed through some processing nodes, and be output as a new record in a database or forwarded to another messaging system. If that sounds complex, Node-RED visualizes these flows as an interactive graph you can manipulate. But enough about Node-RED, you can [install it and try it locally](https://nodered.org/docs/getting-started/local) yourself. We're here today to show you how to launch Node-RED on Fly. And in the process, how to refine an application on Fly for best fit. ## Preparing The first thing to do is to [install Node-RED locally](https://nodered.org/docs/getting-started/local), as there are some command-line operations we'll need later. ## Initialising The simplest way to get up and running with Node-RED on Fly is to use the nodered/node-red docker image. You can do this in one line: ```bash cmd theme={null} fly init appkata-node-red --image nodered/node-red --port 1880 --org personal ``` ```out out theme={null} Selected App Name: appkata-node-red New app created Name = appkata-node-red Organization = personal Version = 0 Status = Hostname = App will initially deploy to lhr (London, United Kingdom) region Wrote config file fly.toml ``` The port is set to 1880 because that's the default port for Node-RED to communicate on. Take a note of where the application says it will initially deploy to too. We can deploy this app right now with: ``` fly deploy ``` and we can open the app with: ``` fly open ``` Thanks to Fly's edge network, your connection will be upgraded automatically to HTTPS and you'll be talking to Node-RED. Or at least observing this warning: ![Warning](https://raw.githubusercontent.com/fly-examples/appkata-node-red/main/images//nodered-data-warning.png) ## Volumes What that error is about is that the /data directory in the Node-RED image doesn't have isn't writable. We need to give our application a writable, persistent storage space, which is where Fly volumes come in. We can create a volume, local to our application, and get Fly to mount it on /data. First step in that process is to create a volume with a name: ```bash cmd theme={null} fly vol create node_red_data --region lhr ``` ```out out theme={null} fly vol create node_red_data --region lhr ID: bjR5jQjvZwNYVCVDxX9R Name: node_red_data Region: lhr Size GB: 10 Created at: 25 Nov 20 11:03 UTC ``` That region setting comes from when we inited the app earlier; it was going to deploy in lhr, so we've made the volume in lhr. The second part of this process is to tell the app about this new volume. For that we edit the `fly.toml` file and where we see: ```toml theme={null} [build] image = "nodered/node-red" ``` we add, after it: ```toml theme={null} [[mounts]] source="node_red_data" destination="/data" ``` This tells Fly that the volume named `node_red_data` should be mounted on the `/data` directory. With that in place, we can `fly deploy` and `fly open` our app. There's no warning and Node-RED flows are safe over redeploys. There's only one problem... ## Securing Node-RED You may have noticed that there's no password authentication when we connect to Node-RED. There is an option to turn it on in the Node-RED settings.js file, but that file is notionally on the /data volume. How can we get a file there with our settings? Let's get the settings.js file first. You can find the default settings file [in the Node-Red repository](https://raw.githubusercontent.com/node-red/node-red/master/packages/node_modules/node-red/settings.js). Download that and save it as `settings.js`. Next, we take control of the build process. ### Switching to a Dockerfile This is where Fly's flexibility comes into play. We can switch deployment from just deploying an image to using a Dockerfile to create our own image. We'll create a Dockerfile to reproduce what we're currently doing first: ``` FROM nodered/node-red ``` And then tell Fly to use the Dockerfile by removing the `[build]` instruction from `fly.toml`, deleting or commenting out the part that reads: ``` [build] image = "nodered/node-red" ``` If we now run `fly deploy` you'll see this instruction being run and the app deploy as it previously did. ### Customizing Startup When images startup, they have a command to be run and its that command we want to take over. The Node-RED image calls npm to start the Node-RED service like so: ``` npm start --cache /data/.npm -- --userDir /data ``` We'll add this command to the end of a short script which will copy our `settings.js` file over to /data/ before we start up Node-RED. This can all go in a script called `entrypoint.sh`. ``` #!/bin/bash cp /etc/settings.js /data/settings.js npm start --cache /data/.npm -- --userDir /data ``` But wait! How did settings.js get there and how are we going to run this script? Back to our Dockerfile, to ask it to do that work. Let's add this to the Dockerfile and: ``` COPY settings.js /etc/settings.js USER root COPY entrypoint.sh / RUN chmod +x /entrypoint.sh USER node-red ENTRYPOINT ["/entrypoint.sh"] ``` So, step by step: * We copy the settings.js file into the image we are creating * We switch to the root user * We then copy the entrypoint.sh script to the file system root * And we make it executable * Then we switch back to the node-red user * And set our entrypoint to this image to our entrypoint script. Two little details to know: One is that the Node-RED Docker image switches to a `node-red` user as its last act, which is why we do the user switcheroo. The other is that yes, if our script was already executable, then it would be executable in the image. But just in case, and because invisible effects are invisible, we explicitly set the script to executable. Now we can `fly deploy` and `fly open` the app again. And everything is exactly the same! Which isn't surprising because we haven't changed the `settings.js` file so it's all still running of defaults. ### Adding a Password Open up the `settings.js` file and find the "Securing Node-RED" section. You'll see an example of what we need to add immediatly below that: it's an `adminAuth` object. Add to the file a section that should look like this: ```javascript theme={null} adminAuth: { type: "credentials", users: [{ username: "admin", password: "hashedpassword", permissions: "*" }] }, ``` This will create us an admin user, but now we need to generate a hashed version of the password. If you installed Node-Red at the start of this example, it'll be easy. Just run `node-red admin hash-pw` and enter your password when prompted. It'll return a string which you should copy in where `hashedpassword` appears above. Now we can `fly deploy` and `fly open` and be greeted with: ![Login Screen](https://raw.githubusercontent.com/fly-examples/appkata-node-red/main/images/login.png) Now we can login as admin and the password we set and we've secured access to our Node-RED. ## Notes We've put together a deployable Node-RED image for Fly. In the process, we've seen how we can take control of existing images and supplement their build process to add in functionality, and how we can use scripts to take over the startup of an image to make modifications we want in place. For Node-RED administration, editing the `setting.js` file locally and deploying will update our installed settings, so you can explore all the optional controls. For Node-RED usage, think of it as a graphically configurable glue layer within your system. You can install many of the third party nodes available and plug into many more applications and platforms. ## Discuss * You can discuss this example on the [community.fly.io](https://community.fly.io/t/node-red-on-fly-now-an-appkata-example/413) topic. # OpenResty: NGINX + Lua Source: https://docs.fly.io/app-guides/openresty-nginx-plus-lua > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Running OpenResty on Fly If you've been developing web applications for long, you've probably heard of [Nginx](https://nginx.org/en/). It's a widely used open-source HTTP server, but it has some limitations. By default, Nginx doesn't provide a way to program logical operators or write custom logic. Developers can circumvent this by using modules like [Nginx JavaScript](https://www.nginx.com/blog/introduction-nginscript/) or [Lua](https://github.com/openresty/lua-nginx-module), but that takes extra work to install and configure. [OpenResty](https://openresty.org/en/) allows you to build full-fledged web applications by bundling Nginx with a Lua compiler and several common modules. This makes OpenResty more broadly useful than vanilla Nginx, but depending on your use case, it could be overkill. For example, if you want to [run a simple reverse proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) on your server, Nginx can handle it. If you need to add rate limiting, authentication, advanced caching, or a connection to a database, you'll need a solution like OpenResty. With the increase in distributed computing and microservices, OpenResty has become an excellent option for complex firewalls, API gateways, and even full-fledged web applications. Typically, OpenResty is deployed to a central server. Web requests go through OpenResty before being routed to the relevant backing services, but this model isn't great for performance or redundancy. While you can set up several servers and run OpenResty on each, you have to figure out how to maintain and sync data across them. A better solution is to use a distributed hosting platform like [Fly](https://fly.io) to run OpenResty at the edge. Using Fly will decrease latency while ensuring that a single node's failure doesn't make your website unavailable. ![Running OpenResty on Fly.io](https://github.com/fly-examples/openresty-basic/raw/main/fly-2020-07-29-a.jpg) ## How to Deploy OpenResty to Fly In this tutorial, you'll see how to create an OpenResty application and deploy it to Fly. You'll create a reverse proxy endpoint that uses the [JSON Placeholder API](https://jsonplaceholder.typicode.com/) as a backend service. You'll use a custom Lua script to add rate limiting, and the Fly Redis connection to add API key authentication to your endpoint. All the steps you need are in this tutorial, but if you'd like to download the final application, it's [available on Github](https://github.com/karllhughes/fly-openresty) as well. ### Prerequisites * [Flyctl command line tool](/flyctl/install). ### Preparing To Fly To start, create a new directory called `fly-openresty` and we'll create your new app inside of it: ```bash theme={null} mkdir fly-openresty && cd fly-openresty ``` ### Configuring the Dockerfile Fly will build and run your Docker image in a [Firecracker VM](/reference/architecture#compute) on the edge, but you need to select and configure a Dockerfile first. OpenResty provides [several Docker images](https://github.com/openresty/docker-openresty) you can use for your application. I opted for Centos, but because of [an apparent bug in their Docker image](https://github.com/openresty/docker-openresty/issues/124), specified the `1.15.8.1-4-centos` tag. After selecting the base image, add a `RUN` command to append `env FLY_REDIS_CACHE_URL` to the top of your `/usr/local/openresty/nginx/conf/nginx.conf` file. This line ensures that Nginx has access to the `FLY_REDIS_CACHE_URL` environment variable. Finally, remove the default Nginx site configuration files and add `default.conf` file to the `/etc/nginx/conf.d/` directory. When done, your Dockerfile should look something like this: ```dockerfile theme={null} # Using this base image because of: https://github.com/openresty/docker-openresty/issues/124 FROM openresty/openresty:1.15.8.1-4-centos # Add the REDIS connection URL as an env variable in NGINX RUN echo -e "env FLY_REDIS_CACHE_URL;\n$(cat /usr/local/openresty/nginx/conf/nginx.conf)" > /usr/local/openresty/nginx/conf/nginx.conf # Add the configuration file RUN rm /etc/nginx/conf.d/* COPY default.conf /etc/nginx/conf.d/default.conf ``` Now that your Dockerfile is ready, you need to create the `default.conf` file before you can deploy your application to Fly. ### Setting up the Nginx Configuration Before you add rate limiting and Redis to your Nginx configuration, start with a simple reverse proxy configuration. To set up your reverse proxy, create a `default.conf` file in your root directory and add the following: ```conf theme={null} server { listen 80; location /api/ { proxy_pass http://jsonplaceholder.typicode.com/posts/; } } ``` This minimal Nginx configuration listens to requests on port 80 and reverse proxies any request to `/api/` to the JSON Placeholder API. Now that your Dockerfile and Nginx configuration are ready, you can create and deploy your reverse proxy to Fly. ### Creating a New Fly Application You'll need to use `flyctl` to create a new application. If you haven't already, install the appropriate version of `flyctl` for your operating system using the [instructions here](/flyctl/install). Next, [sign up](/getting-started/sign-up-sign-in) or [sign in](/getting-started/sign-up-sign-in) to your Fly account via the command line: ```bash theme={null} # Sign up flyctl auth signup # Or sign in flyctl auth login ``` You can now create your new app in the fly-openresty directory using the fly `init` command: ```bash theme={null} flyctl init --port 80 ``` The `--port 80` says our application communicates on port 80. You'll be prompted for an app name. Use the auto-generated app name, select your organization, and select `Dockerfile` as your builder. You should see output similar to this in your console: ```bash theme={null} ? App Name (leave blank to use an auto-generated name) ? Select organization: YOUR ORGANIZATION ? Select builder: Dockerfile (Create an example Dockerfile) New app created Name = Owner = Version = 0 Status = Hostname = Wrote config file fly.toml ``` Fly will create a `fly.toml` file in the your project's directoy and set the `internal_port` to 80 so that requests are properly routed to OpenResty. Now you're ready to deploy your application using the command line: ```bash theme={null} flyctl deploy ``` You should see Fly build your Docker image and push it to Fly to deploy it. When finished, you'll see output similar to the following: ```bash theme={null} Deploying ==> Validating App Configuration --> Validating App Configuration done Services TCP 80/443 ⇢ 80 Deploy source directory '/Users/karl/fly-openresty' Docker daemon available, performing local build... ==> Building with Dockerfile Using Dockerfile: /Users/karl/fly-openresty/Dockerfile ... --> Done Pushing Image ==> Optimizing Image --> Done Optimizing Image ==> Creating Release Release v1 created Monitoring Deployment You can detach the terminal anytime without stopping the deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] --> v1 deployed successfully ``` Your reverse proxy is now live on Fly! You can visit it and see the JSON Placeholder data using the Fly command line tool: ```bash theme={null} flyctl open /api ``` In the next two sections, you'll see how to add rate limiting and authentication using a Redis store and custom Lua scripts. ### Adding Rate Limiting Nginx reads and applies all the configuration files in the `/etc/nginx/conf.d/` directory. Because OpenResty adds the Lua compiler to Nginx, you can write [Lua code](http://www.lua.org/) inside your `default.conf` file. To add rate limiting, you can use the [lua-resty-limit-traffic](https://github.com/openresty/lua-resty-limit-traffic) library that comes with OpenResty and customize its behavior in your Nginx configuration file. Open your `default.conf` file and replace it with the following: ```conf theme={null} lua_shared_dict my_limit_req_store 100m; server { listen 80; location /api/ { access_by_lua_block { -- RATE LIMITER -- local limit_req = require "resty.limit.req" -- Allow .5 requests per second -- local lim, err = limit_req.new("my_limit_req_store", .5, .5) if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err) return ngx.exit(500) end -- Use the visitor's IP addres as a key -- local key = ngx.var.http_fly_client_ip local delay, err = lim:incoming(key, true) -- Throw an error when the limit is reached -- if err == "rejected" then ngx.log(ngx.ERR, "Limit reached: ", err) return ngx.exit(503) end } proxy_pass http://jsonplaceholder.typicode.com/posts/; } } ``` This configuration passes your request through using the `proxy_pass` directive at the end, as the previous version did. Before it does, it checks if the visitor has reached their request limit using the `Fly-Client-IP` header [attached to the request by Fly](/networking/services#http-handler). If the IP address has called the endpoint in the past 2 seconds, it returns a 503 response and logs the error. You can re-deploy this configuration file to Fly (again using `flyctl deploy`) and call the endpoint twice in quick succession to test it. You've now got a working rate limiter, but you're still not finished. In the last section, you'll see how to connect your OpenResty application to Redis to store API keys that can be used for authentication. ### Checking API Keys Against a Redis Store Fly offers a [region-local Redis instance](/upstash/redis) to all deployments, which can be used to persist data for longer periods. While this volatile datastore is not meant for permanent use, you can use it to cache data so that it's accessible on the edge. In this last step, you'll connect to the Fly Redis instance using the OpenResty [Redis driver](https://github.com/openresty/lua-resty-redis). You'll authenticate requests using a `key` passed in by the user through a query string argument and return a 401 response code if authentication fails. In a real application, you would probably push data to the Fly Redis instance using [their global data store](/upstash/redis), but because this is a demonstration app, you'll hard code a few sample API keys. First, write a Lua script to parse the connection string. Fly's `FLY_REDIS_CACHE_URL` must be split at the `:` and `@` characters, so you can write a function that takes any number of characters as possible delimiters ([credit to Walt Howard on Stack Overflow](https://stackoverflow.com/a/29497100/977192) for this one). Create a new file called `split.lua` and add the following: ```lua theme={null} local _M = {} function _M.split(source, delimiters) local elements = {} local pattern = '([^'..delimiters..']+)' string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end); return elements end return _M ``` Next, you need to make sure this file is copied into your Docker image, so open up your `Dockerfile` and add the following line to the end of it: ```dockerfile theme={null} ... COPY split.lua /etc/nginx/conf.d/split.lua ``` Finally, you're ready to update your `default.conf` file. Open it and edit it as shown below: ```conf theme={null} lua_shared_dict my_limit_req_store 100m; lua_package_path "/etc/nginx/conf.d/?.lua;;"; -- Ensures that the Redis connection's DNS resolves -- resolver 8.8.8.8; server { listen 80; location /api/ { access_by_lua_block { -- RATE LIMITER -- ... -- REDIS CACHE -- splitter = require("split") local redis_client = require "resty.redis" local redis = redis_client:new() redis:set_timeouts(1000, 1000, 1000) -- Split the connection string env variable -- parts = splitter.split(os.getenv("FLY_REDIS_CACHE_URL"), ":@") -- Connect to Redis -- local res, err = redis:connect(parts[4], parts[5]) if not res then ngx.log(ngx.ERR, "failed to connect: ", err) return end -- Authorize using the password -- local res, err = redis:auth(parts[3]) if not res then ngx.log(ngx.ERR, "failed to authenticate: ", err) return end -- Set some allowed API keys -- -- Note: This should be done outside this script in a real app -- ok, err = redis:set("oVr0mDgJejSmb9jwXp6B", 1) ok, err = redis:set("AstIqxOHpyAToCwh8qeL", 2) ok, err = redis:set("eaFW03Pjp27ZbgqpgqJQ", 3) -- Lookup the `key` -- local res, err = redis:get(ngx.var.arg_key) if (not res) or (res == ngx.null) then ngx.log(ngx.ERR, "Invalid key: ", err) return ngx.exit(401) end -- Close the connection -- local res, err = redis:close() if not res then ngx.say("failed to close: ", err) return end } proxy_pass http://jsonplaceholder.typicode.com/posts/; } } ``` Deploy this updated configuration file using `flyctl deploy` and visit `https://.fly.dev/api/` again. This time, you will get a 401 response from OpenResty. Add one of the API keys you hard-coded as a `key` in the query string: `https://.fly.dev/api/?key=AstIqxOHpyAToCwh8qeL` and you'll see the JSON placeholder data again. ## Conclusion In this post, you've seen how to create an OpenResty application to extend Nginx's functionality. You've added rate-limiting to ensure that users don't abuse your API and simple authentication using data cached in Redis. Finally, by deploying the application on Fly, you can take advantage of their globally distributed edge hosting environment to make your app faster and more reliable than it would be on traditional hosting. If you have any questions about using OpenResty with [Fly.io](https://fly.io/), be sure to reach out so we can help you get started. # Fly.io and PlanetScale Source: https://docs.fly.io/app-guides/planetscale Connect a Node app on Fly.io to PlanetScale read-only regions, pick the closest replica per region, and replay writes to the primary with fly-replay. > [!WARNING] > > We haven't yet updated this page for Fly Machines, which are the VMs now at the core of all Fly Apps. You might notice that some features don't work the same way anymore. Visit our [community forum](https://community.fly.io/) if you need help with your app. PlanetScale is a MySQL-compatible serverless database. It now supports read replicas so you can perform low-latency reads wherever your users are located. This guide walks you through setting up PlanetScale with Fly.io apps. We'll work with a [sample Express app](https://github.com/fly-apps/nodejs-planetscale-read-replicas/) for the [Fly platform](https://fly.io) which uses a [PlanetScale](https://planetscale.com) database. ## Create a database The sample app assumes a database already exists. You'll need to create that database before running the app. Since read-only regions can currently only be created using the PlanetScale UI, this guide does not use their CLI. [Sign in to PlanetScale](https://auth.planetscale.com/sign-in), creating an account if you don't already have one. You can use a free account to create a database however you will need to upgrade to their paid *Scaler*, *Team*, or *Enterprise* [plan](https://planetscale.com/pricing) if you would like to add read-only regions. Click on the black 'New database' button and pick 'Create new database'. Give it a name and choose its region. This is your **primary** database (we'll refer to it in multiple places later on). Choose the closest region to where you will deploy your app for the best performance: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_new_database_dialog.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=ca50f444e40bb0a872849a351d93d58a) PlanetScale uses branches for schemas. An initial `main` branch will be automatically created. It will take a few seconds to initialise so wait until that notification disappears. You should see a page similar to this: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_database_created.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=72b79452f5d9b2fffaf4175b264a9f5d) Click on the *Connect* button (or on the blue *"connect to your database"* link). A dialog will open showing your username and password. Handily PlanetScale makes connection strings for various clients. For this app we'll choose the one listed to connect with Node.js. When we mention using the connection string for your primary database below, *this* is the one we mean. Make a temporary note of this as it will only show you it once: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_connect_nodejs.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=8673a0b602d8dd4d003b7d799a19d100) There are many SQL clients you could use to manage your database. We'll use [ArcType](https://arctype.com/). That comes with a PlanetScale integration. However to use its PlanetScale integration you need to create an Arctype account. For now we won't. So instead we'll use the standard *MySQL* option. Click the appropriate option in your SQL client to create a new connection: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_arctype_connections.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=b17dccce50bd94047e21991e5ee10586) To save typing in all the details (a username, a password, and so on) we'll use that connection string noted above. Copy the entire value (starting from *"mysql://"*) into this box and then click the "Test connection" button. After a moment you should see a message to say it successfully connected. Click the blue "Save" button to proceed: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_arctype_connection_url.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=273b90297d4f5af8dcebb2df66a3bc6e) Click the blue "New Query" button in the top-right ready to run some basic SQL. Again, your editor will look slightly different: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_arctype_new_query.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=a5ca899a885265158e948e462d45933c) We'll use the names of some fruits as some sample data. So paste this into your query editor and click the "Run" (or equivalent) button: ```sql theme={null} CREATE TABLE `fruits` ( `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255) NOT NULL ); ``` You should see it succeeded: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_arctype_create_table_succeeded.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=97a8b65c44d72d5aacde637f00cbe7fd) Add a few rows to that table: ```sql theme={null} INSERT INTO `fruits` (id, name) VALUES (1, 'banana'); INSERT INTO `fruits` (id, name) VALUES (2, 'apple'); INSERT INTO `fruits` (id, name) VALUES (3, 'strawberry'); ``` Promote the main branch to production. The production branch is protected, highly available and is automatically backed up. You can promote a branch within the PlanetScale UI by simply clicking this button: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_promote.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=cda2d7fea9cd6b32aafd9ea5ed1c702d) Assuming you have a *Scaler*, *Team*, or *Enterprise* account, you will be able to add additional read-only regions. They can greatly improve the performance of your application, reducing the latency of reads from regions close to them. Click on the *main* branch (the production one). You should see the option to *Add region*: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_add_region.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=a7834b0c8f090ac14b2b018d1cbb0613) Choose the region you would like to create it in: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_add_region_dialog.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=98ccd75971930b8856a3f7aec510a78a) After a few moments those additional read-only regions should be ready. They may take slightly longer if there is a large amount of data to be replicated. Each region has its own, unique credentials. So click the "..." and then on *Connect*. It will show you a username, password and host. Make a connection string for each region that will look like this: `"mysql://**username**:**password**@**host**/**database-name**?ssl={"rejectUnauthorized":true}"`: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_add_region_connect.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=40f77456fba507174684a13c47b07b72) Your database is now ready to be queried by our sample app! ## Run the app locally To run it locally you will need [NodeJS](https://nodejs.org/en/download/). 1. Clone the [`nodejs-planetscale-read-replicas`](https://github.com/fly-apps/nodejs-planetscale-read-replicas) repo. 2. Run `npm install` to install its dependencies. 3. Duplicate `.env.example`, naming it `.env`. 4. The application needs to know about all the available regions. There are different ways this could be done. We've opted to use a single comma-separated string of connection strings. In the `.env`, set `DATABASE_URL` as *all* of the connection URLs you have, separated by a comma. The **first** one should be the one for your **primary** (and possibly only) database. Follow it with those for any read-only regions you've added. It will look something like this. Note the comma between them: ``` DATABASE_URL='mysql://user1:password1@region1/name?ssl={"rejectUnauthorized":true},mysql://user2:password2@region2/name?ssl={"rejectUnauthorized":true}' ``` 5. Run `npm start`. 6. You should be able to visit `http://localhost:3000` and see `hello world`: that confirms the app is running. 7. Visit `http://localhost:3000/read` and you should see a list of fruits fetched from PlanetScale. Note when run locally, this sample app does not know where you are in the world and so it can't pick the closest database. It will default to using the *first* connection string (which must be the primary). 8. Visit `http://localhost:3000/write` and you should see a row has been added to the PlanetScale database. Normally data is added by a non-GET request however using a GET makes it easier to check using a browser. You've successfully connected to a PlanetScale database! ## Deploy the app to Fly If you haven't already done so, [install the Fly CLI](/flyctl/install) and then [log in to Fly](/getting-started/sign-up-sign-in). We've added a few files to make this sample app ready to run on Fly: a `Dockerfile`, `.dockerignore` and `fly.toml`. The `Dockerfile` tells Fly how to *package* the application. The `.dockerignore` file specifies the files/folders we want included (else it would include ones like `.env` that we do not want). The `fly.toml` file *configures* the application. The Fly CLI can make that for you. But since we know which variables, ports, services and so on our app needs, we made one. **Note:** You'll need to make two changes to the provided `fly.toml`: 1. Update the app's name at the top to one of your choice `app = "your-name-here"`. 2. Update the region to the one nearest *your* primary database: `PRIMARY_REGION = "lhr"`. We created *our* database in `eu-west`. But Fly uses different region names than PlanetScale. And so you need to use the Fly region you would want your primary PlanetScale database to always be used from. As an example, let's say you have a PlanetScale database in *eu-west-1*. You plan on having a Fly app deployed to `lhr` and `iad`. You would set `PRIMARY_REGION = "lhr"` in the `fly.toml`, because that is the closest Fly region to the primary database's region. To deploy the app, run `fly launch` from the application's directory. The Fly CLI will spot the existing `fly.toml`: ``` An existing fly.toml file was found for app-name ? Would you like to copy its configuration to the new app? (y/N) ``` Type *y* (yes). The CLI will then spot the `Dockerfile`: ``` Scanning source code Detected a Dockerfile app ``` You'll be asked to give the app a name. Type a name using lowercase characters and hyphens, for example **your-app-here**. You'll be prompted to pick an organization. Since every Fly user has a personal organization, let's pick that for now. You'll be asked for the region to deploy the first vm to. That will be the same value you set for `PRIMARY_REGION` above. For example, `lhr`. It will ask if you want a PostgreSQL database. Type *N* (no) as you are using PlanetScale for its database! Finally you are asked if you want to deploy. Type *N* (no). Why not deploy now? This sample application expects a `DATABASE_URL` connection string to be available as a secret environment variable. Now that your app is staged on Fly we can set that. So run this command (setting its value as a comma-separated list of *all* of the connection strings you have from PlanetScale). The sample app expects the **first one** to be the connection string for your **primary database** (the region you created the PlanetScale database in). Any *subsequent* ones are the connection strings for any read-only regions. The resulting command you need to run will look something like this. Note the comma *between* the connection strings: ```bash theme={null} fly secrets set DATABASE_URL='mysql://your-primary-region-one,mysql://optional-for-read-region,mysql://optional-for-another-read-region' ``` You should see `Secrets are staged for the first deployment`. Now you can go ahead and deploy the app: ```bash theme={null} fly deploy ``` You should see the build progress and after a couple of minutes the application will be successfully deployed: ``` ==> Monitoring deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 2 passing] --> v0 deployed successfully ``` If you open its URL `https://your-app-here.fly.dev` you should see the "hello world" text to confirm the app is working. If you now open its *read* page `https://your-app-here.fly.dev/read` you should see a list of fruits read from the *closest* PlanetScale database. And since the primary region is the closest, well it makes sense to use that: ```json theme={null} { "time": 15, "usingFlyRegion": "lhr", "usingPrimaryRegion": true, "usingDatabaseHost": "random.eu-west-3.psdb.cloud", "data": [ { "id": 7, "name": "grape" }, { "id": 6, "name": "grape" }, { "id": 5, "name": "lemon" } ] } ``` If you open its *write* page `https://your-app-here.fly.dev/write` you should see a random row of data has been written. Again, this request is handled by a vm in the primary region and so the write is sent to the *primary* PlanetScale database: ```json theme={null} { "time": 18, "usingFlyRegion": "lhr", "usingPrimaryRegion": true, "usingDatabaseHost": "random.eu-west-3.psdb.cloud", "data": "Added a row with ID 8" } ``` ## Scaling the app So far the app has *only* used the primary database. Since there is only *one* vm. Now we'll test the read-only regions. Start by permitting the app to run in more regions. You can see which regions the app can run in using: ```bash theme={null} fly regions list ``` You'll see something like: ``` Region Pool: lhr Backup Region: ``` Adjust that to permit more regions. For our test database, we made a primary in `eu-west-1` (Ireland), and two read-only regions in `us-west-2` (US) and `ap-southeast-2` (Australia). Suitably distributed to test the latency. And so we'll adjust the regions our sample Fly app can run in to use roughly matching Fly regions: ```bash theme={null} fly regions set lhr sea syd ``` Now we need to scale the app to put a vm in each of those regions: ```bash theme={null} fly scale count 3 --max-per-region=1 ``` **Note:** If you run `fly scale show` you may see `Max Per Region: Not set`. This is a known issue however the value *is* set. Confirm the new vms have been placed by waiting a few moments and then running `fly status`. Wait a minute until all three have a status of `running`: ``` Instances ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED abcdefgh app 2 syd run running 2 total, 2 passing 0 33s ago abcdefgh app 2 sea run running 2 total, 2 passing 0 33s ago abcdefgh app 2 lhr run running 2 total, 2 passing 0 1m2s ago ``` **Note:** You may want to run `fly logs`. If you have not edited our sample app to remove our debug logs, you should see each vm making a choice at run-time for which database it should connect to. For example for the vm launched in `sea` *we* saw *... the closest available database should be this one: us-west.connect.psdb.cloud*. For the vm launched in `syd` we *instead* saw: *...the closest available database should be this one: aws-ap-southeast-2.connect.psdb.cloud*. Perfect! Each of those vms connected to the *closest* database. You may need to adjust your region preferences (please take a look at `database.js` to see how we picked them). Back to the test. Now those two new vms are running, try calling the `https://app-name-here.fly.dev/read` and the `https://app-name-here.fly.dev/write` again. This time look at the value of `"usingFlyRegion"` in the response to see where the request was handled. If that value is your primary region (in our case that was `lhr`) any read-only regions *won't* be used. As there is no *need* for them to be. So to test any read-only regions you need to make a request from somewhere *else*, closer to one of the other vms. In our case, we want to make a request that will be served by the vm we have in the US. That could be done in a variety of ways. You could use an online tool. We opted to use another Fly app we have to simulate requests from a particular location. It has a vm in the `sjc` (US) region. We connected to that vm using `fly ssh console` and ran a couple of curl commands ... #### Read ``` #curl https://app-name-here.fly.dev/read {"time":19,"usingFlyRegion":"sea","usingPrimaryRegion":false,"usingDatabaseHost":"us-west.connect.psdb.cloud","data":[{"id":9,"name":"lemon"},{"id":8,"name":"blueberry"},{"id":7,"name":"grape"}]} ``` Perfect! We can see from that response that this request worked. It was handled by the vm in `sea` (the closest vm *this* app has to our simulated user in `sjc`). The response shows which database it fetched the data about our fruits from: the read-only region `us-west.connect.psdb.cloud`! This confirms it didn't need to connect to the primary database, far away in Europe. A fast, low-latency read. #### Write ``` #curl https://app-name-here.fly.dev/write #{"time":20,"usingFlyRegion":"lhr","usingPrimaryRegion":true,"usingDatabaseHost":"random.eu-west-3.psdb.cloud","data":"Added a row with ID 10"} ``` Perfect! We can see from that response that this request worked. It was handled in our primary region (`lhr`). And so the write succeeded. Despite the closest database to `sea` actually being a read-only replica. But wait ... how does this work? Well, we can see by looking at the logs for this app: ``` 2022-05-19T19:09:42Z app[abcdefgh] sea [info]Replaying this attempt to write to a read replica (from Fly region: sea) in the primary Fly region: lhr ``` That line is logged by our app's custom error handler, in `server.js`. Initially the write request was received by the closest vm to the simulated user (in the US). And that vm connected to the *closest* database (in the US). But ... that's a read-only replica. And so that write failed, throwing an error. That error was caught by the error handler, analysed and matched. And so the app used some Fly magic to *replay* the HTTP request *in* the primary region (in our case that's `lhr`). And a vm in that region connects to the primary database. Which *can* of course be written to. And so the write worked. ## How does it know which database is closest? If there is only one connection string in the `DATABASE_URL`, no decision needs to be made. That is used regardless of where in the world the app is running. That would be the case if you don't have any read replicas. If there are multiple connection strings in the `DATABASE_URL`, the app needs to decide which one of them it should connect to. If you take a look at our sample app's `database.js` you will see it takes a *rough* guess at which regions it would *prefer* to connect to (ideally the closest). And then it sees which of those regions actually has a database. By looking for a connection string that uses a host in that region. If it can't find one, it defaults to connecting to the primary. If your app only uses a couple of regions, it would probably be simpler for you to instead have *more* environment variables and *less* code. You could add an environment variable per region, such as `DATABASE_URL_LHR`, `DATABASE_URL_FRA` ... and so on. And then pick the appropriate one based on the value of `FLY_REGION` that is provided at run-time as an environment variable. You don't have to do it the same way we did! ## Issues If you try deploying the app to Fly and that fails, the most *likely* reason is your `DATABASE_URL` contains at least one invalid connection string. When each vm starts, the app tries to connect to the database. If it can't (such as the provided password is wrong) it will throw an error. You can confirm that is the case by running `fly logs` after a failed deploy. If you see a line such as `sea [info]Error: Access denied for user 'username-here'` please check all your connection strings are correct and there is no space between the commas. ## Notes * If you have any read-only replicas and use this approach (replaying a write to them using Fly's `fly-replay` header) make sure to not enable auto-scaling for your app. There is currently an issue where vms are not evenly distributed and so you can be sure there will be a vm in your primary region. And if not, writes to the database will fail. * For this example we used the same branch for local development *and* on Fly. In reality you would have a development branch and a separate production branch. As such, the connection string(s) in your local `.env` file would differ from the one your application uses on Fly. And in fact would likely instead be a local installation of MySQL to reduce costs. * Adding additional regions in PlanetScale increases the cost of storage. For example if your primary database is 10GB, adding an additional region (for a read replica) would double the cost of storage. * Make sure to keep an eye on usage. If you exceed your included monthly allowance you are billed an additional fee: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_misc_statistics.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=56784828b611ea9599429d620cfb1b06) * Since the usage is based on rows read (not rows fetched) make sure all of your queries are optimised. If they don't use an index, they could result in a table scan which will increase your costs. * Your application may *not* benefit from adding read-only regions if it does substantially more writes than reads. In which case the saving on latency for reads may not justify the increased storage cost. ## View your application on Fly Use `fly apps open` as a shortcut to open the app's URL in your browser. Use `fly logs` to see its log files. Use `fly status` to see its details: ``` App Name = your-app Owner = Version = 1 Status = running Hostname = your-app.fly.dev Deployment Status ID = 67c6d3a4-963f-46cf-b694-d7e28aea6600 Version = v1 Status = successful Description = Deployment completed successfully Instances = 1 desired, 1 placed, 1 healthy, 0 unhealthy Instances ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED abcdefgh app 1 lhr run running 2 total, 2 passing 0 0h1m ago ``` ## Delete the app To avoid charges you might want to delete the app and the PlanetScale database: * To delete the sample Fly app (this is irreversible), run `fly apps destroy the-app-name-here` * To delete the PlanetScale database, click the red delete button within the database's *Settings* tab: ![Screenshot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_misc_delete.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=27f6c73b98de4ab74273726699931420) # Postgres: A single server deployment for Fly Source: https://docs.fly.io/app-guides/postgres > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Postgres on Fly Deploy a Postgres database on Fly, ready for your Fly apps. # Rationale Postgres is a useful database to have as part of your Fly installation. To make it simple to install, this example takes you through all the steps necessary. The resulting Postgresql installation will available over the Fly 6PN internal network to other applications and to users who have configured a Fly Wireguard VPN on their local system. This Postgres configuration is good for development and small services. For mission-critical workloads, you'll want a HA configuration. ## Initialize the Fly app Create your `fly.toml` file by importing the template from the repository. Run: `fly init --import fly.source.toml` You will be prompted for an app name and organization. You can hit return to generate an app name when asked. Do make a note of the name of the app and the region that it will initially deploy to. You'll need the app name to connect to it, and more immediately, the region for the next step. ## Create a volume Postgres needs to be able to store and persist data on disk. For that Fly has volumes which can be created in all of the Fly regions. Each volume is region-specific and is identified by name. `fly volumes create postgres --region lhr` Would create a 10GB volume named `postgres` in the `lhr` region. Add `--size 20` to create a 20GB volume. ## Set an initial password When Postgres initialised the database for the first time, it will use the `POSTGRES_PASSWORD` to set the password for the `postgres` super-user. To pass that to the database, we set a Fly secret with whatever password we have chosen. `fly secrets set POSTGRES_PASSWORD=` ## Deploy Postgres We are now ready to deploy Postgresql. Run: `fly deploy` ## Connecting to Postgres As configured, this Postgres has no direct external connectivity. ### Other Fly Apps When 6PN private networking is enabled for an app, the Postgres instance will be accessible on `.internal`. It will not require TLS as connections between apps within Fly are already encrypted. ### Wireguard If you have configured Fly's Wireguard, then you can securely connect to the Postgres server using that. The address of the server will be `.internal`. For example, your first connection to your new Postgresql with psql would be: `psql postgres://postgres:@appname.internal/` ## Notes * This configuration has no direct external connectivity and is specifically for the use of other Fly apps * This configuration is good for development and small services. ## Discuss * You can discuss this example on the [postgres example topic](https://community.fly.io/t/new-postgres-example/458) on community.fly.io. # Puppeteer JS Renderer Source: https://docs.fly.io/app-guides/puppeteer-js-renderer > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Puppeteer JS Renderer on Fly JavaScript is the bane of a web scraper's life. Scraping is all about extracting data from a web page and JavaScript is there adding content, hiding blocks, and moving the DOM around. Reading the HTML from the server is just not enough. What you ideally want is a way to run all that JavaScript on the page so you can see what's left after that. Then you can get down to some serious scraping. There are tools to do this out there but most have their own complications or restrictions that make them difficult to deploy. Puppeteer has none of those problems and with [Fly](https://fly.io), you can deploy puppeteer based applications close to your users. At its core, this project is a puppeteer-based service. [Puppeteer](https://pptr.dev/) is a package that renders pages using a headless Chrome instance, executing the JavaScript within the page. ## Quick Start A typical YouTube page will add the views count to itself only when the JavaScript on that page executes. These are the types of use cases where Puppeteer comes in very handy. Puppeteer can execute the JavaScript on the YouTube page and, from the rendered page, extract the view count and the title of the video. Lets get started ### Install example project To install this project locally, execute the following commands: ``` git clone https://github.com/fly-examples/puppeteer-js-renderer.git cd puppeteer-js-renderer npm install ``` ### Your first scraper To quickly try the project out after installing it, run the command below: ``` node yt-views.js ``` This will show something like below: ``` Pulling views from YouTube, please wait... Luis Fonsi - Despacito ft. Daddy Yankee has 6,881,463,846 views ``` If you want to try any other video just pass the YouTube video URL as a parameter to the script like below: ``` node yt-views https://www.youtube.com/watch?v=XqZsoesa55w ``` It should give you an output like this: ``` Pulling views from YouTube, please wait... Baby Shark Dance | Sing and Dance! | @Baby Shark Official | PINKFONG Songs for Children has 6,077,338,169 views ``` ### What just happened here? To show the YouTube video views count, a small scraper written with [Axrio](https://www.npmjs.com/package/@geshan/axrio) npm package was executed. Axrio combines the popular [Axios](https://www.npmjs.com/package/axios) library and [Cheerio](https://www.npmjs.com/package/cheerio) to create a mini scraper. Axios is used to make requests and Cheerio acts like DOM navigator parsing the markup and giving us an API for traversing/manipulating the resulting data structure. You can kickstart a small scraper with Axrio too. The `yt-views.js` is a basic scraper which performs a GET request for the given YouTube URL with Puppeteer. This will return the final DOM after the page's JavaScript executes. Then it parses the title and views count out of the rendered page's markup and prints it on the console. It uses the puppeteer-js-renderer service that is already deployed and running on Fly.io. Have a look at he "[how to use it as a service](#how-to-use-it-as-a-service)" section for more information. To start your own instance on Fly.io jump to the "[how to deploy on fly.io](#how-to-deploy-it-on-fly-io)" section. ## Run locally If you have node installed on your machine, you already cloned this repository and ran `npm install`. Now run `npm start` to get this service running locally. The next step is to navigate to `http://localhost:8080/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk` on your browser to view the JavaScript rendered output. ### Run with Docker If you want to run with Docker, execute the following after you clone the repository: ```sh theme={null} cd puppeteer-js-renderer docker-compose up ``` Then go to `http://localhost:8080/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk` in your browser to see the output. ## How to use it as a service If you want to use puppeteer-js-renderer for scraping, you can use the following URL on Fly.io: ``` https://js-renderer-fly.fly.dev/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk ``` The YouTube URL is an example above, you can use any URL that needs javascript to be executed to be functional. ### Styles broken Styles and images will look broken but the HTML tags will be there. Happy Web Scraping! ## How to deploy it on fly.io [Fly.io](https://fly.io) is a platform for applications that need to run globally. It runs your code close to users and scales compute in cities where your app is busiest. Write your code, package it into a Docker image, deploy it to Fly's platform, and let that do all the work to keep your app snappy. Fly.io has great [documentation](/) to get started. You can find a quick speed run showing how to get your app running closer to your users with this [guide](/getting-started/launch). Please follow these steps to deploy your own puppeteer-js-renderer service on Fly.io: ### Prerequisites 1. [Install](/flyctl/install) the flyctl CLI command. 2. Register on fly with `flyctl auth signup`, if you already have a fly account log in with `flyctl auth login`. ### Steps 1. Clone this repo with `git clone git@github.com:fly-examples/puppeteer-js-renderer.git` if you are logged in with SSH support enabled. Otherwise try `git clone https://github.com/fly-examples/puppeteer-js-renderer.git`. 2. Then run `cd puppeteer-js-renderer`. 3. After that execute `flyctl init --dockerfile` and when asked for an app name, hit return to have one generated (unless there's a name you really want). I ran it with js-renderer-fly as the app name for this example. 4. Subsequently, you can select an organization. Usually this will be your first name-last name on the prompt. 5. It should create a fly.toml file in the project root (I have not committed it, it is in .gitignore). 6. Now run `flyctl deploy` to deploy the app. It will build the docker image, push it to the fly docker image registry and deploy it. In the process of deploying it, it will display information about the number of instances and their health. 7. You can then run `flyctl info`. This will give the details of the app including hostname. 8. You can view your app in the browser with `flyctl open`. For me, it opened `https://js-renderer-fly.fly.dev` and displays `{message: "alive"}`. 9. Add `/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk` on the address bar of your browser after your puppeteer-js-renderer URL. Mine looked like `https://js-renderer-fly.fly.dev/api/render?url=https://www.youtube.com/watch?v=kJQP7kiw5Fk`. This will render the final DOM of that YouTube page after the JavaScript is executed. 10. Enjoy! The YouTube video URL is an example and it can, of course, be replaced with other web pages which need JavaScript to work. ## More fly commands You can suspend your service with `flyctl suspend`; this will pause your service until you resume it. If you try `flyctl status` after suspending it will not show any instances running. Suspending the service means all resources allocated to run the service will be deallocated, resulting in the application running nowhere. To get the instances running again execute `flyctl resume`. ### Fly default resources I wanted to see what resources were allocated to the App on Fly by default. The scale commands allowed me to find it out pretty easily like below: 1. `flyctl scale show` - showed me VM Size: micro-2x 2. `flyctl scale vm` - showed me micro-2x is a 0.25 CPU cores with 512 MB of memory. If you want to increase CPU/memory or run more instances in a particular region please refer to the official Fly docs on [scaling](/launch/scale-count). ### Flying the app on 3 continents Our service is running in one data center. For me, it's iad (Ashburn, Virginia) but yours will likely be different based on where you are working from. We can add instances around the world to speed up responses, let's get rolling: 1. To see the regions available run `flyctl platform regions`, I could see regions all over the world from Oregon to Sydney. 2. Let's add an instance to Australia in Sydney, to do this run `flyctl regions add syd`, yes it is that easy. 3. Now check `flyctl status` and you will see an instance running in Sydney 4. Let’s add one more in Europe in Amsterdam with `flyctl regions add ams`. So now we are mostly covered with the app running in 3 continents. 5. Of course, you can run `flyctl status` again to see your app shining on 3 continents. # Redis - standalone Redis Server Source: https://docs.fly.io/app-guides/redis > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** The official repository for Running Redis on Fly.io. Find the accompanying Docker image at [flyio/redis](https://hub.docker.com/repository/docker/flyio/redis). ## Usage This installation requires setting a password on Redis. To do that, run `fly secrets set REDIS_PASSWORD=mypassword` before deploying. Keep track of this password - it won't be visible again after deployment! If you need no customizations, you can deploy using the official Docker image. See `fly.toml` in this repository for an example to get started with. ## Runtime requirements By default, this Redis installation will only accept connections on the private IPv6 network, on the standard port 6379. If you want to access it from the public internet, add a `[[services]]` section to your `fly.toml`. An example is included in this repo for accessing Redis on port 10000. We recommend adding persistent storage for Redis data. If you skip this step, data will be lost across deploys or restarts. For Fly apps, the volume needs to be in the same region as the app instances. For example: ```bash cmd theme={null} flyctl volumes create redis_server --region ord ``` ```out out theme={null} Name: redis_server Region: ord Size GB: 10 Created at: 02 Nov 20 19:55 UTC ``` To connect this volume to the app, `fly.toml` includes a `[mounts]` entry. ``` [mounts] source = "redis_server" destination = "/data" ``` When the app starts, that volume will be mounted on /data. ## Cutting a release If you have write access to this repo, you can ship a prerelease or full release with: ``` scripts/bump_version.sh ``` or ``` scripts/bump_version.sh prerel ``` # Appkata: Redistls - Redis Server with TLS Source: https://docs.fly.io/app-guides/redistls > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Redis (with TLS) Deploying Redis with TLS encryption using Fly. [Read this guide on Fly.](/app-guides/redistls) ## Rationale A key/value database like Redis is useful to have to support caching, session management and, well, anything which needs simple fast storage. That's why there's Redis already built into Fly. But there are some things that Redis configuration doesn't do, like Publish and Subscribe, and that's when you want to deploy your own Redis on Fly. For this example, we are going to customize a Redis docker image to tune it for running on Fly and deploy it with persistent disk storage for Redis to save its data on. ## Preparing There's a couple of components to this example. We're going to use the official Redis image, `redis:alpine`, but we want to change some system settings before Redis starts running. To do that, we'll use a script, `start-redis-server.sh` ```shell theme={null} #!/bin/sh sysctl vm.overcommit_memory=1 sysctl net.core.somaxconn=1024 # pass env vars as server args redis-server /usr/local/etc/redis/redis.conf --requirepass $REDIS_PASSWORD ``` The two `sysctl` calls set up the environment so that Redis doesn't throw warnings about memory and connections. The script then starts up the Redis server, giving it a password to require and a config file to boot with. The Redis config is simple – it defines IPs to listen on, persistence options: ```conf theme={null} # listen on all ipv4 and ipv6 addresses bind 0.0.0.0 :: # write to /data/ dir /data/ # appendonly settings appendonly yes auto-aof-rewritepercentage 100 auto-aof-rewrite-min-size 64mb ``` Now we need to make those changes apply to a new Redis deployment. For that we use this `Dockerfile`: ```dockerfile theme={null} FROM redis:alpine ADD redis.conf /usr/local/etc/redis/redis.conf ADD start-redis-server.sh /usr/bin/ ADD certs /etc/certs RUN chmod +x /usr/bin/start-redis-server.sh CMD ["start-redis-server.sh"] ``` It adds our new shell script to the image, makes it executable, adds the redis config, and boots the container with the shell script. With these two files in place we're ready to put Redis onto Fly. ## Configuring First, we need a configuration file - and a slot on Fly - for our new application. We'll use the `fly init` command. The parameter is the app name we want - names have to be unique so choose a new unique one or omit the name in the command line and let Fly choose a name for you. ```bash cmd theme={null} fly init redis-example ``` ```out out theme={null} Selected App Name: redis-example ? Select organization: Demo Sandbox (demo-sandbox) ? Select builder: Dockerfile (Do not set a builder and use the existing Dockerfile) ? Select Internal Port: 6379 New app created Name = redis-example Organization = demo-sandbox Version = 0 Status = Hostname = App will initially deploy to ord (Chicago, Illinois (US)) region Wrote config file fly.toml ``` The important choices here are we select a Dockerfile (the one we created) as the builder for this app and we set the internal port to 6379, the default port for Redis. Do take note of which region here the init command says the app will initially deploy into. We'll need that in a moment. This will generate a new fly.toml file which we'll need to edit. By default, the `fly.toml` takes the internal port and connects it to an HTTP only handler on port 80 and a combined TLS/HTTP handler on port 443. We don't need that at all, we just need a straight-through connection to the internal port. Edit your generated `fly.toml`, removing both `[[services.ports]]` entries for ports 80 and 443 and replacing them with a single entry: ``` [[services.ports]] handlers = [] port = "10000" ``` This will direct external traffic on port 10000 to internal port 6379. Save the file for now. ## Keeping a secret Our script takes a password from an environment variable to secure Redis. We need to set that now using the `fly secrets` command. This encrypts the value so it can't leak out; the only time it is decoded is into the Fly application as an environment variable. ``` flyctl secrets set REDIS_PASSWORD= ``` And of course, remember that password because you won't be able to get it back. ## Persisting Redis The last step is to create a disk volume for Redis to save its state on. Then the Redis can be restarted without losing data. For Fly apps, the volume needs to be in the same region as the app. We saw that region when we initialized the app; here it's `ord`. We'll give the volume the name `redis_server`. ```bash cmd theme={null} flyctl volumes create redis_server --region ord ``` ```out out theme={null} Name: redis_server Region: ord Size GB: 10 Created at: 02 Nov 20 19:55 UTC ``` To connect this volume to the app, pop back to editing fly.toml and add: ``` [[mounts]] source = "redis_server" destination = "/data" ``` When the app starts, that volume will be mounted on /data. ## Deploy We're ready to deploy now. Run `fly deploy` and the Redis app will be created and launched on the cloud. Once complete you can connect to it using the `redis-cli` command or any other Redis client. Just remember to use port 10000, not the default port. ``` redis-cli -h redis-example.fly.dev \ -p 10000 \ --askpass ``` You'll immediately be prompted to enter the password you previously set. ## Lock it down with TLS So far, our Redis configuration is good for a demo. But running Redis on an unencrypted public port with password authentication is insecure. Redis has built-in TLS support, we generate certificates and configure the server to require client certificate validation. This is roughly the equivalent security level as running behind a VPN. Generating certs is simple with the excellent [`mkcert`](https://github.com/FiloSottile/mkcert) utility. Once you install `mkcert`, you'll get a local certificate authority to create your own certificates with. Here are the commands you'll need to create certificates for this Redis example: 1. Create `certs` directory for the Docker image: ``` mkdir -p certs ``` 2. Generate server certificates: ``` mkcert -key-file certs/redis-server.key -cert-file certs/redis-server.crt redis-example.fly.dev ``` 3. Generate a client certificate: ``` mkcert --client -key-file redis-client.key -cert-file redis-client.crt redis-example.fly.dev ``` 4. Copy the root CA certificate: ``` cp "$(mkcert -CAROOT)/rootCA.pem" certs/rootCA.pem ``` When you're done you should see a directory layout like this: ``` ├── certs │   ├── redis-server.crt │   ├── redis-server.key │   └── rootCA.crt ├── redis-client.crt └── redis-client.key ``` Then, we can pop into our `redis.conf` to configure TLS by adding: ```conf theme={null} tls-port 7379 tls-cert-file /etc/certs/redis-server.crt tls-key-file /etc/certs/seredis-server.key tls-ca-cert-file /etc/certs/rootCA.pem ``` This configures Redis to listen on port 7379, which means changing the internal port in `fly.toml`: ```toml theme={null} [[services]] internal_port = 7379 ... ``` Now run `fly deploy` again, and enjoy the relaxation of a locked-down Redis service. The `redis-cli` CLI has support for TLS, you can connect with this command: ``` redis-cli -h redis-example.fly.dev \ -p 10000 \ --cert redis-client.crt \ --key redis-client.key \ --tls \ --cacert certs/rootCA.pem \ --askpass ``` As before, you will be prompted for a password, but now the connection has already been authenticated by the TLS handshaking exchanging the client certificate. ## Discuss * You can discuss this example on the [community.fly.io](https://community.fly.io/t/new-redistls-appkata-example/405) topic. # Global Image Processing Service Source: https://docs.fly.io/app-guides/run-a-global-image-service ![A hand holding scissors and cutting out paper for a scrapbook](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/image-service.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=3f638dc1b705e59b0be13a090b14da8b) > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** For many web applications, the ability to scale, crop and otherwise manipulate images is essential. One way of providing this capability is with a URL-request-driven image service which can retrieve images, process them and deliver them wherever needed. But that also means provisioning the service globally to keep latency times low which can be its own administrative headache. With Fly you can eliminate all that administrative load by letting Fly deploy a service on demand close to your users so they get a rapidly rendered experience. In this guide, we'll show you how to deploy a popular image processing application, Imaginary, into the Fly Global Application Platform to make your own Global Image Service. ## *What’s Imaginary* Let's start with [h2non/Imaginary](https://github.com/h2non/imaginary), a fantastically useful image processing application written in Go. Once configured, it lets you process images. The images can be posted to it or you can ask it to get an image from a remote server and all of this is controlled through a URL request to the Imaginary server. You can find all the source in the GitHub repository, but for this guide, we don't need to worry about that. We're going to use the developer-supplied Docker image to deploy it. ## *Deploying Docker Images to Fly* How do you turn a Docker image into a globally available edge application? With Fly it's remarkably concise process. One thing we know at Fly is that our users want to deploy more than JavaScript at the edge. Powerful though that is, they also want fully fledged application stacks and backend components to live at the edge too. Now they can, and in this series of guides we'll show you how to use them. In this first guide, we're going to take an existing application and make it go global with Fly. ## *Creating the Dockerfile* To use a Docker image, we'll need a Dockerfile. Create a new directory and create a `Dockerfile` in it with these contents. ```dockerfile theme={null} FROM h2non/imaginary:1.1.1 ENV PORT "8080" CMD ["-enable-url-source","-http-read-timeout" ,"3"] ``` If you aren't familiar with Docker, the first `FROM` line selects the image of the application and version we want to use. The Imaginary image is configured to run the `imaginary` command. Imaginary, running in a Docker container, normally opens a port on port 9000, but for this exercise, we want to open its port on 8080, Fly's default port for external communications. The simplest way to do that is to set the PORT environment value to 8080. The third `CMD` line contains the parameters we pass to the `imaginary` command that are going to configure how Imaginary runs. The `-enable-url-source` tells Imaginary it is allowed to retrieve images from around the internet and, just so it's well-behaved, we set the `-http-read-timeout` to 3 seconds (the default is 60 which is a long time to wait for an image). ## *Testing Locally* You'll need to have Docker installed locally to test this locally. We won't go into the details of installing Docker - check out the [Docker site and guides](https://docs.docker.com/install/) for how to install. Assuming you've done this we can move on to testing. #### Build a Docker Instance To create a container instance using the Dockerfile, run: ```bash theme={null} docker build -t localimaginary . ``` #### Run the Docker Instance To start up the Docker container, run: ```bash theme={null} docker run -p 8080:8080 localimaginary ``` Where does that -p 8080:8080 value come from? Well, it's exposing port 8080, the one we set in the `ENV` line in our docker file. #### Processing an Image To test our local image, let's process a file. In this case, we're going to crop an existing image from Imaginary's github repository. In another terminal session, run: ```bash theme={null} curl -O "http://localhost:8080/crop?width=500&height=400&url=https://raw.githubusercontent.com/h2non/imaginary/master/testdata/large.webp" ``` This will ask Imaginary to crop, to 100x100, an image stored in the Imaginary GitHub repository. This should result in a file, `large.webp` being written to disc which you can display using your preferred image viewer. MacOS users can simply `open large.webp` at the command line. Open the cropped image. Take a moment to play with the width and height values in the URL we're querying and you will get different sized cropped images. Once you are happy the local build is working, you can return to the session where you started the docker image and ctrl-C to stop it. We now have a working docker image. Now to globally deploy it with Fly. ## *Going Global* For this you'll need flyctl, it's your CLI-powered remote control for your Fly applications. ### Installing `flyctl` If you've already installed `flyctl`, move on to the next step. If not, hop over to [our installation guide](/flyctl/install). ### Sign In (or Up) for Fly If you have a Fly account, all you need to do is [sign-in with `flyctl`](#sign-in-or-up-for-fly). ##### New User? Welcome to Fly. To get your Fly account run: ```bash theme={null} flyctl auth signup ``` This will take you to the sign-up page where you can either: * **Sign up with Email**: Enter your name, email and password. * **Sign up with GitHub**: If you have a GitHub account, you can use that to sign up. Look out for the confirmatory email we will send you which will give you a link to set a password; you'll need a password set so we can actively verify that it is you for some Fly operations. ##### Returning User Run: ```bash theme={null} flyctl auth login ``` Your browser will open up with the Fly sign-in screen. Enter your user name and password to sign in. If you signed up with GitHub, use the `Sign in with GitHub` button to sign in. Whichever route you take you will be signed-up, signed-in and returned to your command line, ready to Fly. ## Preparing to fly ### Creating a `fly.toml` File The flyctl app helps you handle the entire lifecycle of your Fly Global application. That all starts with creating the `fly.toml` file which will control how your application is configured when deployed. To do so, run: ```bash theme={null} flyctl apps create ``` This will kick off a short dialogue with you about your new application. First up, it'll ask for an application name: ```output theme={null} ? App Name (leave blank to use an auto-generated name) flyimaginary ``` Your name has to be unique across all Fly users, so unless there's a good reason, go with an auto-generated name. Next you'll be asked to select an organization. If this is your first time on Fly, there'll only be one organization - your own personal organization, just for your applications. Organizations are all about sharing apps and collaborating. For this guide, we don't need to worry about that, so we can use our personal organization. ```output theme={null} ? Select organization: Demo (demo) ``` Once the organization has been selected, `flyctl` gets to generating the app. ```output theme={null} New app created Name = flyimaginary Owner = demo Version = 0 Status = Hostname = Wrote config file fly.toml ``` So, what's in `fly.toml`? If you look in it, you'll find various configuration parameters. You can skip to Deploying if you want to. ```toml theme={null} app = "flyimaginary" [[services]] internal_port = 8080 protocol = "tcp" [services.concurrency] hard_limit = 25 soft_limit = 20 [[services.ports]] handlers = ["http"] port = "80" [[services.ports]] handlers = ["tls", "http"] port = "443" [[services.tcp_checks]] interval = 10000 timeout = 2000 ``` As well as the app name in the file, there's a services section which sets what the internal port number exposed by the app is and how it should be handled. It also says this port should be handled as TCP. That's followed by `services.concurrency` which sets soft and hard limits to the number of connections the application should take before being transparently scaled horizontally. There are also two `services.ports` sections. The first says incoming connections on port 80 will be routed to port 8080 and handled as HTTP. The second, more interesting entry, says that incoming connections on port 443 will be handled as TLS terminated HTTP connections and then routed on as HTTP to port 8080. There's also a `services.tcp_checks` section which is used to define how we do health checks on the application. We can leave all of this completely untouched for this guide. We're ready to Fly globally. ### Deploying For deployment, we go back to flyctl. ```bash theme={null} flyctl deploy ``` Flyctl will find the application name from the `fly.toml` file automatically and start the deployment process. There'll be a fair amount of output as flyctl gets Docker to assemble the image, create a release of the image and then deploy and check it's running. When it is complete you'll see something like: ```output theme={null} ... ==> Creating Release Release v0 created ==> Monitoring Deployment You can detach the terminal anytime without stopping the deployment v0 is being deployed 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] v0 deployed successfully ``` The application is now deployed. ### Viewing Fly Apps You'll need the URL for the deployed application to connect to it. Flyctl helps out here with the `flyctl info` command: ```bash cmd theme={null} flyctl info ``` ```output output theme={null} App Name = flyimaginary Owner = demo Version = 0 Status = running Hostname = flyimaginary.fly.dev Services TASK PROTOCOL PORTS app tcp 443 => 8080 [TLS, HTTP] 80 => 8080 [HTTP] IP Addresses TYPE ADDRESS CREATED AT v4 77.83.140.214 5m36s ago v6 2a09:8280:1:a93f:aeee:f28f:2c4e:2fb1 5m35s ago ``` ### Testing Imaginary on Fly This tells you all you need to know about your currently deployed application. If you take the `Hostname` you can use that in a URL request to the Fly deployment: ```bash theme={null} curl -O "http://flyimaginary.fly.dev/crop?width=500&height=400&url=https://raw.githubusercontent.com/h2non/imaginary/master/testdata/large.webp" ``` Notice that we didn't need to specify port 8080 as we did with the local docker build. That's because port 80 on the host routes to port 8080. As an added bonus, we also got HTTPS support as port 443 also routes to port 8080, with Fly managing the TLS termination and the Let's Encrypt certificates. We can do the same query, over a secured connection, with: ```bash theme={null} curl -O "https://flyimaginary.fly.dev/crop?width=500&height=400&url=https://raw.githubusercontent.com/h2non/imaginary/master/testdata/large.webp" ``` ## *Summary* In this guide we created a Docker image, based on a third-party image, tested it locally then deployed it to the Fly platform using `flyctl`. You now have a Global Image Service. Whenever you request it processes an image, Fly will create an instance of the service closest to you to fulfill your request. # Run A Private DNS Over HTTPS Service Source: https://docs.fly.io/app-guides/run-a-private-dns-over-https-service > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** ## *Why DNS over HTTPS?* DNS over HTTPS (or DoH or 🍩) is a protocol that makes browsing more private. Browsers typically resolve domain names with an unencrypted protocol, allowing nosy neighbors and internet providers to snoop on some internet activity. DoH creates an encrypted connection between browsers and the DNS resolver to make it difficult to even see what domains a user is loading. Firefox now [uses DNS over HTTPS by default](https://blog.mozilla.org/blog/2020/02/25/firefox-continues-push-to-bring-dns-over-https-by-default-for-us-users/) in the US and [DNS over HTTPS can easily be enabled](https://support.mozilla.org/en-US/kb/firefox-dns-over-https) elsewhere in the world. With the release of version 83, Chrome has gained official support for DNS over HTTPS (see this [Chrome Blog post](https://blog.chromium.org/2020/05/a-safer-and-more-private-browsing-DoH.html) for details). And Microsoft is planning to enable [DNS over HTTPS in Windows 10](https://techcommunity.microsoft.com/t5/networking-blog/windows-will-improve-user-privacy-with-dns-over-https/ba-p/1014229). It's useful to be able to run private services for protocols like DoH. If you pipe all your encrypted DNS requests to one shared provider, you're essentially just swapping one nosy ISP for an even bigger internet provider. Fly is an especially good place to run DNS services because we deploy apps globally and the internet is faster when DNS is close by. And if you want to, you can even [manage your own TLS](#handle-your-own-tls). ## *Prerequisites* ### Install Fly CLI If you're on a Mac, you can install the CLI with Homebrew: ```bash theme={null} brew install flyctl ``` For other systems, download and run the install script: ```bash theme={null} curl -L https://fly.io/install.sh | sh ``` ### Sign In (or Up) for Fly If you have a Fly account, all you need to do is [sign-in with `flyctl`](#returning-user). #### New User? Welcome to Fly. To get your Fly account run: ```bash theme={null} flyctl auth signup ``` This will take you to the sign-up page where you can either: * **Sign up With Email**: Enter your name, email and password. * **Sign up With GitHub**: If you have a GitHub account, you can use that to sign up. Look out for the confirmatory email we will send you which will give you a link to set a password; you'll need a password set so we can actively verify that it is you for some Fly operations. #### Returning User Run: ```bash theme={null} flyctl auth login ``` Your browser will open up with the Fly sign-in screen. Enter your user name and password to sign in. If you signed up with GitHub, use the `Sign in with GitHub` button to sign in. Whichever route you take you will be signed-up, signed-in and returned to your command line, ready to Fly. ### Create a Fly App Pick a name for your new DNS over HTTPS service. Then create a Fly app: ```bash cmd theme={null} flyctl apps create ``` ```output output theme={null} ? App Name (leave blank to use an auto-generated name) > [your-app-name] ✔ New app created Name = [your-app-name] Owner = fly Version = v0 Status = Created fly.toml ``` ## *Two-minute easy mode* We've published a [Docker image](https://hub.docker.com/r/flyio/doh-proxy) you can use to get started. ### 1. Deploy `flyio/doh-proxy` from Docker Hub ```bash cmd theme={null} flyctl deploy -i flyio/doh-proxy:0.1.19 ``` ```output output theme={null} ==> Validating app configuration --> done Services TCP 80/443 ⇢ 8080 ==> Creating Release Release v0 created ==> Monitoring Deployment You can detach the terminal anytime without stopping the deployment v0 is being deployed 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] v0 deployed successfully ``` ### 2. Use `https://[your-app-name].fly.dev/dns-query` Your new Fly app includes dedicated IP addresses and a `[your-app-name].fly.dev` hostname with a valid certificate. You can see these by running `flyctl info`: ```bash cmd theme={null} flyctl info ``` ```output output theme={null} App Name = doh-proxy Owner = fly Version = 10 Status = pending Hostname = https://[your-app-name].fly.dev Services TASK PROTOCOL PORT INTERNAL PORT HANDLERS app tcp 80 8080 http app tcp 443 8080 tls http IP Addresses ADDRESS TYPE 77.83.140.25 v4 2a09:8280:1:68a1:90f6:e320:d275:70f7 v6 ``` The URL for DNS queries is: `https://[your-app-name].fly.dev/dns-query`, you can use it in any app that supports DNS over HTTPS. You can also try it out with `curl`: ```bash cmd theme={null} curl -D - -sS "https://doh-proxy.fly.dev/dns-query?ct&dns=q80BAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE" | strings ``` ```output output theme={null} HTTP/2 200 content-length: 56 content-type: application/dns-message x-padding: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX cache-control: max-age=43200 date: Wed, 11 Sep 2019 15:08:54 GMT server: Fly/a04ef97 (Wed, 11 Sep 2019 11:18:30 +0000) example ``` ## *Hard mode (maybe 10 minutes)* ### 1. Write a Custom `Dockerfile` We use Docker to build and package applications. For custom builds, you'll need a `Dockerfile` in your working directory — here's a good one for `doh-proxy`: ```dockerfile theme={null} # ------------------------------------------------------------------------------ # Cargo Build Stage # ------------------------------------------------------------------------------ FROM rust:latest as cargo-build ARG VERSION=0.1.19 RUN apt-get update RUN apt-get install musl-tools -y RUN rustup target add x86_64-unknown-linux-musl RUN RUSTFLAGS=-Clinker=musl-gcc cargo install doh-proxy --version $VERSION --root /usr/local/ --target=x86_64-unknown-linux-musl # ------------------------------------------------------------------------------ # Final Stage # ------------------------------------------------------------------------------ FROM alpine:3.10 RUN apk add --no-cache libgcc runit shadow curl COPY --from=cargo-build /usr/local/bin /usr/local/bin RUN useradd doh-proxy USER doh-proxy ENV LISTEN=0.0.0.0:8080 ENV RESOLVER=9.9.9.9:53 RUN /usr/local/bin/doh-proxy --version CMD ["sh", "-c", "/usr/local/bin/doh-proxy --listen-address $LISTEN --server-address $RESOLVER"] ``` ### 2. Deploy working directory with `flyctl` The Fly CLI will build + deploy a Docker image in one step. Just run `flyctl deploy` in your working directory. If Docker is running locally, we'll do a local build. If you don't have docker installed, we copy the contents of your working directory and perform a build on our servers. ```bash cmd theme={null} flyctl deploy ``` ```output output theme={null} ==> Validating app configuration --> done Services TCP 80/443 ⇢ 8080 Deploy source directory '.' Docker daemon available, performing local build... Using Dockerfile from working directory: Dockerfile Step 1/15 : FROM rust:latest as cargo-build ... Step 6/15 : RUN RUSTFLAGS=-Clinker=musl-gcc cargo install doh-proxy --version $VERSION --root /usr/local/ --target=x86_64-unknown-linux-musl ... Step 7/15 : FROM alpine:3.10 ... Step 9/15 : COPY --from=cargo-build /usr/local/bin /usr/local/bin ... Step 15/15 : CMD ["sh", "-c", "/usr/local/bin/doh-proxy --listen-address $LISTEN --server-address $RESOLVER"] ---> Running in 427a770ed8a5 ---> ee696da27723 Successfully built ee696da27723 Successfully tagged registry.fly.io/srcproxy:deployment-1580379849 Image size: 18 MB ==> Pushing image The push refers to repository [registry.fly.io/srcproxy] 75b521405184: Pushed ec42405b9867: Pushed fecdd1368b2a: Pushed 531743b7098c: Pushed deployment-1580379849: digest: sha256:220099d5aae94190a206bfdda219480ffbf8da988690361ae6d1d280732d8a68 size: 1158 --> done ==> Creating Release Release v0 created ==> Monitoring Deployment You can detach the terminal anytime without stopping the deployment v0 is being deployed 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing] v0 deployed successfully ``` This could take a while on the first one. Subsequent runs will reuse image layers and finish quite a bit faster. ## *Optional configuration* ### Use a Different Resolver The `doh-proxy` project defaults to using the [Quad9\* DNS resolver](https://www.quad9.net) at `9.9.9.9:53`. To change this, you can add an environment variable: ```bash cmd theme={null} flyctl secrets set RESOLVER=8.8.8.8:53 ``` ```output output theme={null} VERSION REASON DESCRIPTION USER DATE v10 Secrets updated flydev@fly.local 0s ago ``` ### Change Hostnames Fly apps support an unlimited number of hostnames and SSL certificates. To set one up: 1. Add a certificate to your fly app: ```bash cmd theme={null} flyctl certs create example.com -a doh-proxy ``` ```output output theme={null} Hostname = example.com Configured = false Certificate Authority = lets_encrypt DNS Provider = nsamesystem DNS Validation Target = example.com.q8o1.flydns.net Source = fly ``` 2. Create a validation CNAME entry: * Name: `_acme-challenge.example.com.` * Target: `example.com.q8o1.flydns.net` 3. Make a DNS entry pointing example.com to your Fly app IP ### Handle your own TLS Fly can take care of TLS + HTTPS for you, but it doesn't have to. If you'd prefer to terminate your own TLS, you can generate your own certificates, store them as encrypted Fly secrets, and tweak [the Dockerfile](#1-write-a-custom-dockerfile) to pull them from your app environment. We'll happily forward encrypted TCP to your app and let you worry about the rest. # Smokescreen - Sanitize outbound web requests Source: https://docs.fly.io/app-guides/smokescreen > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # Smokescreen Managing your outgoing web connections from Fly with Smokescreen ## Rationale This example runs [Stripe's Smokescreen proxy](https://github.com/stripe/smokescreen) on Fly, with appropriate modifications to add basic password control. It is always worthwhile to control the outgoing traffic from your other applications. If your apps call other systems with user-entered URLs, say for triggering webhooks or reading responses from an API, then there is a possibility that that feature could be abused. A bad actor could enter URLs designed to access resources inside your application's private network, giving it the names of known machines or well-known private IP addresses. Depending on how your app responds may give that bad actor a clue on how your application works and from that, possibly stage an attack. To handle this, Stripe created (and open-sourced), Smokescreen, an outbound proxy that makes sure that requests to the outside world from your applications aren't trying to probe your internal network. Out of the box, Smokescreen will check any request isn't destined for 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16" or fc00::/7. There's a lot more that Smokescreen can do around roles, ACLs, and TLS certificates, but that is outside the scope of this example. ## The Dockerfile One thing with Smokescreen is that there is no binary executable distributed for it. That means it has to be built to deploy it. This example comes with a Dockerfile that does a multi-stage build - first compiling the code, then moving the executable to a clean image to run. ## Deploying on Fly The quickest way to initialize the app is to import the `fly.source.toml` file supplied: ``` fly init smokescreen-example --import fly.source.toml ``` Replace `smokescreen-example` with your preferred app name (`yourappname`) or omit it to have Fly generate a name for you. You may be prompted for which organization you want the app to run in. Ensure that it is running in the same organization as the apps you wish to manage their external calls. Smokescreen runs on port 4750 and applications wishing to connect to it from within your Fly organization should send their requests to `yourappname.internal`. To connect to Smokescreen from outside the Fly environment, use [Fly's WireGuard](/blueprints/connect-private-network-wireguard) to create a VPN into your Fly organization. This Smokescreen example includes support for a proxy password and an access control list. Before you deploy Smokescreen, you'll need to set a Fly secret for the proxy password; if it is not set, Smokescreen will exit immediately. ``` fly secrets set PROXY_PASSWORD=yourpassword ``` To bring Smokescreen online, run: ``` fly deploy ``` ## Testing To test Smokescreen is running correctly, you can use `curl`. The -x option on curl tells it to use the following address and port as a proxy. As Smokescreen has a password set, we also have to use the -U option to send our password. Therefore the command: ```bash theme={null} curl -U anyname:yourpassword -x yourappname.internal:4750 https://fly.io ``` Would attempt to use the proxy to contact the secure version of the fly.io site. It'll echo back the contents of the front page. Remember for this to work, you'll need to configure a Wireguard VPN into your Fly organization. If you don't, the `.internal` host name resolution will not work. If an attempt was made to connect to `localhost`, as a network mapper may do, this would happen: ```bash theme={null} curl -U anyname:yourpassword -x yourappname.internal:4750 http://localhost/ Egress proxying is denied to host 'localhost': The destination address (127.0.0.1) was denied by rule 'Deny: Not Global Unicast'. destination address was denied by rule, see error. ``` ## Notes * The configuration shown is an example, with a simple password lock on the authentication. As it currently stands, if the connection is "authed" (that is, has a valid password), then the ACL will let the connection take place and note it in the logs: ```yaml theme={null} --- version: v1 services: - name: authed project: users action: report default: project: other action: enforce ``` The `enforce` action only allows connections to sites in the `allowed_domains` list (which as there aren't any, means it blocks all unauthorized connections). Read more about ACLs in the [Smokescreen README](https://github.com/stripe/smokescreen#acls). The `authed` users requests have a `report` action which allows the connection to be made and logs information about that connection. Running `fly logs` will show entries like this: ```log theme={null} 2021-01-13T11:35:13.843Z be1e33dd lhr [info] time="2021-01-13T11:35:13Z" level=info msg=CANONICAL-PROXY-DECISION allow=true content_length=0 decision_reason="rule has allow and report policy" dst_ip="2a09:8280:1:a270:b8f5:b9f7:8891:f3" dst_port=443 enforce_would_deny=true id=bvvdlsd11c3s4uipn73g project=users proxy_type=connect requested_host="fly.io:443" role=authed source_addr="[fdaa:0:4:a7b:dc6:0:a:2]:53137" src_host="fdaa:0:4:a7b:dc6:0:a:2" src_host_common_name=unknown src_host_organization_unit=unknown src_port=53137 start_time="2021-01-13 11:35:13.865538649 +0000 UTC" trace_id= 2021-01-13T11:35:14.022Z be1e33dd lhr [info] time="2021-01-13T11:35:14Z" level=info msg=CANONICAL-PROXY-CN-CLOSE bytes_in=0xc000137310 bytes_out=0xc000137318 dst_ip="2a09:8280:1:a270:b8f5:b9f7:8891:f3" dst_port=443 duration=0.170710048 end_time="2021-01-13 11:35:14.062381945 +0000 UTC" error= id=bvvdlsd11c3s4uipn73g last_activity="2021-01-13 11:35:14.062118835 +0000 UTC" proxy_type=connect requested_host="fly.io:443" role=authed source_addr="[fdaa:0:4:a7b:dc6:0:a:2]:53137" start_time="2021-01-13 11:35:13.865538649 +0000 UTC" trace_id= ``` The first entry records the connection being allowed, the second the completion of that connection. ## Discuss * Discuss the Smokescreen example on its [dedicated community.fly.io topic](https://community.fly.io/t/new-smokescreen-example/466) # VS Code - Remote Server over SSH Source: https://docs.fly.io/app-guides/vscode-remote > [!WARNING] > > **Warning: This document is old! It is likely wrong in some important way.** # VSCode-Remote Deploy a remote development system for VS Code's Remote SSH on Fly. ## Rationale When you get the need to develop but there's no machine to hand that's quite powerful enough, your next step may be to roll yourself a cloud instance and develop on there. With tools like VS Code's Remote SSH mode, remotely working with a development machine in the cloud can be a joy. But then you have to go through the entire provisioning process every time you want a fresh machine to work with. Which is why using Fly can make it simpler. This example is all about making that process completely automated. At the end of deploying it, you'll have a Fly instance with attached disk storage, running Ubuntu, accessible by you and using certificate-based authentication, with Docker and whatever packages you select. ## Using Get the code by git cloning the repository: ``` git clone https://github.com/fly-examples/vscode-remote.git ``` Then cd into the `vscode-remote` directory and run the configure script. ```bash cmd theme={null} ./configure.sh ``` ```out out theme={null} Enter desired app name or press return to have a name generated: Enter desired organization name or press return to use your personal org: Enter disk size in GB or press return for default 10GB: Use Docker on remote machine (y/n):y Any extra packages:clang htop make ``` We'll pause here to review the only interactive part of the process. * First, there's a request for an app-name which is used for the instance's hostname. If you don't need a particular name, just hit return and Fly will generate an appname. * That's followed by a request for an organization. Just hit return to put it into your personal organization. * Then there's a request for the number of gigabytes of disk space you want attached to the instance. This will be your working space. Hit return to get the minimum (and default) of 10GB. * If you want Docker's daemon and tools pre-installed, answer Y at the next question. * You can add other Ubuntu packages to be pre-installed at this point. Hit return for none, or enter their names in a space-separated list. Why is this pre-installation important you may ask. Remember that this is the image that we will start with and the image that Fly will reload when an instance is restarted or moved. Not pre-installing it means that, although you could install something, it would be completely transitory and disappear when the instance was restarted. Unless you made provision to install things on the persistent disk space (not simple for system packages). So thats why we ask about Docker and extra packages. From this point on, the deployment process happens without intervention. #### Creating the configuration The script starts work by creating a template (`import.toml`) and using that to create a new Fly app. This template is custom created and includes copies of the public keys of the user which are then used to authenticate SSH logins to the app later. This then automatically configures the app to work with SSH on port 10022 externally and port 22 internally. ``` Importing configuration from import.toml Importing port 22 New app created Name = crimson-bush-6560 Organization = personal Version = 0 Status = Hostname = App will initially deploy to lhr (London, United Kingdom) region Wrote config file fly.toml ``` `The created`fly.toml\` file already has an entry in it for a persistent volume called data. That now needs to be created. #### Creating a persistent volume The script gets the region the app was created in (here `lhr`) and creates the volume in it. ``` ID: p0Kg0L0JRGjBluq1PD Name: data Region: lhr Size GB: 10 Created at: 02 Dec 20 11:04 UTC ``` It is now able to go ahead and build the image for deployment. #### Building the image At this point, the script runs `fly deploy` which itself does two things; build the app's image and deploy the results. The building is done by processing the `Dockerfile`. ``` Deploying crimson-bush-6560 ==> Validating App Configuration --> Validating App Configuration done Services TCP 10022 ⇢ 22 Deploy source directory '/Users/dj/temp/vscode-remote' Docker daemon available, performing local build... ==> Building with Dockerfile Using Dockerfile Builder: /Users/dj/temp/vscode-remote/Dockerfile Step 1/17 : FROM ubuntu:bionic ---> 2c047404e52d ... ``` There's a lot more output that comes here with the creating and installation of all the packages required, along with any other packages requested. We've skipped it here, but rest assured we end up here next: ``` Step 17/17 : CMD ["/usr/sbin/sshd", "-D"] ---> Running in 765dba7b0c10 ---> b63be4ff51c8 Successfully built b63be4ff51c8 Successfully tagged registry.fly.io/crimson-bush-6560:deployment-1606907053 --> Building with Dockerfile done ``` The next part sees the new image first pushed to the fly.io repository, and then after being processed and optimized, deployed to the Fly infrastructure. ``` Image: registry.fly.io/crimson-bush-6560:deployment-1606907053 Image size: 488 MB ==> Pushing Image The push refers to repository [registry.fly.io/crimson-bush-6560] ... deployment-1606907053: digest: sha256:99806ec0460487aaaa8d4899f02471e10c39e9d98a424efda58a9e63e7e2251d size: 2400 --> Done Pushing Image ==> Optimizing Image --> Done Optimizing Image ==> Creating Release Release v0 created Deploying to : crimson-bush-6560.fly.dev Monitoring Deployment You can detach the terminal anytime without stopping the deployment 1 desired, 1 placed, 1 healthy, 0 unhealthy --> v0 deployed successfully To use in VS Code, tell the remote-ssh package to connect to dj@crimson-bush-6560.fly.dev:10022 ``` The very last thing we are told is the address we need to give to VS Code when we do "Remote-SSH -> Connect to Host". Open VS Code, bring up the command line (Command/Control-Shift-P), enter `remote-ssh` and select `Connect to Host`. You'll be prompted to `Select configured SSH host or enter user@host`. We'll do the latter, and enter (as per our example) `dj@crimson-bush-6560.fly.dev:10022`. You'll be prompted to confirm the fingerprint of the machine you are connecting to (hit return) and then a new VS Code window will appear, with everything - file system, language server, tools and terminals running on the remote machine. ## Inside the process Now, at this point, if you're happy launching your own VS Code SSH instances on Fly and don't need to know more you can go. If you are curious on how this example is built, then read on. ### The Dockerfile The heart of many Fly apps is the Dockerfile, which creates the image that will be sent up to Fly to run. In this case, it uses Ubuntu Bionic as a base then: * installs a number of essential packages (SSH server) and anything selected by the user as an extra package. * creates a user with a selected name. * gives that user `sudo` privileges. * installs Docker components if they were requested * copies over SSH server configuration and keys * sets up the image to run The important part here is that variables, `USER`, `USE_DOCKER` and `EXTRA_PKGS` are used to configure some of that build behavior. ### The Script The `configure.sh` script is responsible for getting the values of those variables. It asks the user running it for settings for the various values. It also asks for an appname (as `$appname`), an organization (as `$orgname`), and a disk size for the attached volume. Finally, it gathers the user's SSH public keys into a single string called AUTHORIZED\_KEYS. With all the values acquired, the script can create the app. ### Creating with a Template To create the app, the script writes out a file called `import.toml`. It's a template for what we would like in our application when we create it. It sets up SSH's port 22 to appear as port 10022, adds a mount for a disk volume, and passes SSH AUTHORIZED\_KEYS as an environment variable. Then it runs: ``` fly init $appname --import import.toml --org $orgname --overwrite ``` This creates an app with the user's requested app name and organization and then imports the `import.toml` file to configure it. It places the app in the user's `personal` organization. Finally, an `--overwrite` flag means this command will overwrite existing `fly.toml` files without querying the user. ### Finding the Region/Creating a Volume Now, this script needs to create a volume in the same region as the app to provide storage for it. While humans can easily read the the output saying which region the app has been created in, it's currently a little harder for scripts. That's where this snippet comes in: ``` REGION=`fly regions list --json | awk '/"Code"/ { sub(/\ *"Code\": \"/ , "") sub(/",/,"") print }'` ``` It runs the `fly regions list` command but with the `--json` flag which makes the output somewhat more processable. It then uses `awk` (a fine Unix command) to extract out the region code from the line which reads `"Code": "lhr",`. It's that value it saves into the REGION variable ready for when it runs: ``` fly volumes create data --region $REGION $disksize ``` Which creates the storage volume. ### Deploying with Docker Arguments At this point in the script we are ready to deploy with `fly deploy` but we still have our `USER`, `USE_DOCKER` and `EXTRA_PKGS` arguments to pass. The command line looks like this: ``` fly deploy --build-arg USER=$(whoami) --build-arg EXTRA_PKGS="$extrapackages" $usedocker ``` The `--build-arg` flag takes a name=value pairing as its parameter. The first build-arg sets `USER` to the value of the current user's name. The second build-arg sets `EXTRA_PKGS` to the value of the \$extrapackages variable; notice that it is surrounded in quotes are the list of extra packages will likely contain spaces which the shell would process. Finally, we have `$usedocker` on its own, but that's because, if you scan to the top of the script, you'll see that it the user answers "Yes" to the question, the value of \$usedocker is set to "--build-arg USE\_DOCKER=y" (if they answer no, then \$usedocker is just an empty string). As you can see, there's a lot of ways to build up your build arguments. Consult your shell and Dockerfile references for more. Once run, the command will build, using the Dockerfile and build-args, an image which will then be deployed onto the Fly infrastructure. The only thing left is the tell the user how to connect and what address to use. ``` echo "To use in VS Code, tell the remote-ssh package to connect to $(whoami)@$(fly info --host):10022" ``` The `fly info --host` is an easy way to get the host name of your deployment for connection strings. ## Discuss * You can discuss this example on the [vscode-remote example](https://community.fly.io/t/a-vscode-example-for-fly/460) topic on community.fly.io. # App Availability and Resiliency Source: https://docs.fly.io/apps/app-availability Fly.io resiliency features, from two Machines per process group and standby Machines to health check routing, and what your app must handle itself. ![Illustration by Annie Ruygt of a figure jumping from one mountian to another](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/migrating-from-aws.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=805c99c4914a71cef1ff3c36c73019bb) 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 First, it's important to understand what your app needs to take care of. Since Fly Proxy does load balancing by default, you need to consider how your app handles: * session storage, so that users stay logged in when their session moves to a different server * persistent file storage and replication, as discussed in the following paragraphs > [!NOTE] > > **A note about Fly Proxy**: > Fly Proxy does a lot of work in the background, applying handlers, managing traffic and load balancing, and managing connections to services. Fly Proxy also automatically starts and stops Machines based on traffic to your app. The proxy works for [process groups with services defined](/networking/app-services#services-routed-with-fly-proxy). For databases, you'll need to follow the specific recommendations for the database you're using, including how to set up clusters for replication and failover. If you're deploying Fly Postgres, our un-managed database app, then you need to [manage backups, recovery, replication, and scaling](/unmanaged-postgres) yourself. For information about high availability with Fly Postgres, refer to [High Availability & Global Replication](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication). When you use [Fly Volumes](/volumes) with your app, your app needs to implement replication and backups. Volumes are block devices that live on a single disk array and get mounted directly by your Machine. There's no magic. And there's no built-in replication between volumes. If your app provides a clustering mechanism with data replication, like most databases do, we recommend you take advantage of that and run multiple instances with attached volumes. When possible, we place your app's volumes in different hardware zones within a region to mitigate hardware failures. This brings us to disaster recovery and being prepared. You should have backups of your data and understand how to recover that data if needed. Fly.io takes daily snapshots of volumes and stores them for 5 days by default. You can [restore a volume's data](/volumes/snapshots#restore-a-volume-from-a-snapshot) from one of these daily snapshots. If your use case requires more frequent backups, then you'll need to set up tooling and processes to backup your volumes on the required schedule. ## Fly.io features for app resiliency The Fly Platform has features to help make your app more resilient in case of outages or hardware failures. The features for redundancy and availability are: * [Redundancy by default on first deploy](#redundancy-by-default-on-first-deploy) * [Automatically start and stop Machines](#automatically-start-and-stop-machines) * [Health check-based routing](#health-check-based-routing) > [!NOTE] > > These features require or depend on your app configuration in the `fly.toml` file, including process groups. Every Fly App has at least one process group. An app with no extra process groups defined in `fly.toml` still uses the `app` process group by default. Learn more about [app configuration](/reference/configuration). ## Redundancy by default on first deploy You get redundancy by default on first deploy and when you deploy after scaling down to zero Machines. This feature helps you deploy a recommended setup without having to think about it. If the defaults in the following sections aren't what you're looking for, then you can still configure your app and processes the way you want. Learn more about [resilient apps and multiple Machines](/blueprints/resilient-apps-multiple-machines), [scaling Machine CPU and RAM](/launch/scale-machine), and [scaling the number of Machines](/launch/scale-count). Here's what Fly Launch (the `fly launch` or `fly deploy` command) does, based on your app configuration: * creates and starts [two Machines](#two-machines-for-process-groups-with-services) for process groups with services, sets automatic start and stop to true and minimum machines running to zero * creates and starts one Machine and creates one stopped [standby Machine](#standby-machines-for-process-groups-without-services) for process groups without services * creates and starts only one Machine if the process group has volumes mounted, because there's no built-in replication between volumes ### Two Machines for process groups with services The most basic way to improve resiliency is to create more than one Machine per process group, and the `fly launch` and `fly deploy` commands do this by default for process groups with services. A process group with services has mappings to the external internet or to a private (Flycast) network, so that Fly Proxy can manage its connections and operation. If a process group has services defined, two Machines are automatically created and started when: * you deploy an app for the first time using `fly launch` or `fly deploy`, * you redeploy an app using `fly deploy` after scaling down to zero, or * you add a new process group with services in the `fly.toml` file and then run `fly deploy` ### Standby Machines for process groups without services Standby Machines provide low-effort redundancy for process groups with no services defined. A process group with no services has no mappings to the external internet or to a private (Flycast) network, so Fly Proxy can't manage its connections and operation. A standby Machine is a stopped Machine that's essentially paired to and watching a running Machine. The standby starts only if the paired Machine becomes unavailable. If a process group doesn't have services defined, then a standby Machine is automatically created, along with a running Machine, when: * you deploy an app for the first time using `fly launch` or `fly deploy`, * you redeploy an app using `fly deploy` after scaling down to zero, or * you add a new process group in fly.toml and then run `fly deploy` The standby Machine doesn't consume resources or start up until it's needed. #### Remove a standby Machine To remove a standby Machine, run `fly status` and copy the ID of the Machine that displays the `†` symbol beside the process group name. For example: ``` PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED task† 784e469a443e38 43 yul stopped 2024-08-06T20:48:10Z ``` Then destroy the standby Machine with `fly machine destroy `. If you destroy a standby Machine, it is not recreated when you [scale up or back down](/launch/scale-count) using `fly scale count`. If you add services for a process group in `fly.toml` that previously had no services, then the standby designation is removed from the standby Machine on the next deploy. #### Create a standby Machine You can recreate the standby Machine if you scale down to 0 and then run `fly deploy`, which will create one Machine and one standby Machine again. At a lower level, you can also use the `fly machines update` command with the `--standby-for` option to create a standby configuration using two existing Machines. ### Turn off redundancy on deploy What if you don't want to keep Machines running all the time for an app with a small or variable workload? The [automatic start and stop feature](#automatically-start-and-stop-machines) is enabled by default for Machines and makes it possible to run at least two Machines without wasting resources. If you still don't want `fly launch` or `fly deploy` to create two Machines, then you can use the `--ha` option to turn off this feature. Create one Machine for a new app: ```bash theme={null} fly launch --ha=false ``` Create one Machine on first deploy or when your app is scaled down to zero Machines: ```bash theme={null} fly deploy --ha=false ``` Scale down an app to one Machine: ```bash theme={null} fly scale count 1 ``` For apps with more than one process, refer to [Scale a process group horizontally](/launch/processes#scale-a-process-group-horizontally). ## Automatically start and stop Machines Automatic start and stop works for process groups with services defined. This feature enables you to have multiple Machines that only run when needed. Fly Proxy can start and stop existing Machines based on incoming requests, so that your app can accommodate bursts in demand without keeping extra Machines running constantly. And if your app needs to have one or more Machines always running in your primary region, then you can set a minimum number of machines to keep running. Fly Proxy is also responsible for [load balancing](/reference/load-balancing). Default settings for new apps created using the `fly launch` command: automatically start and automatically stop Fly Machines, and minimum machines running is zero. Default settings for some existing apps (or any apps that don't have these settings in `fly.toml`): automatically start but don't automatically stop Fly Machines. Learn more about [how Fly Proxy autostop/autostart works](/reference/fly-proxy-autostop-autostart) and [how to configure it](/launch/autostop-autostart). ## Health check-based routing Fly Proxy routes network connections away from Machines that are failing health checks. Health check-based routing works for process groups with services defined and is based on the configuration of [`services.tcp_checks`](/reference/configuration#services-tcp_checks) or [`services.http_checks`](/reference/configuration#services-http_checks) in your app's `fly.toml` file. If the configured health checks are failing for a Machine, then the proxy doesn't route network connections to that Machine. The proxy routes connections to a healthy Machine instead. If there aren't any healthy Machines, then the connection will block, waiting for a Machine to become healthy. > [!NOTE] > > Health check-based routing doesn't work for the top-level health checks defined in the `checks` section of fly.toml, because the top-level checks don't apply to specific services. # App handover guide Source: https://docs.fly.io/apps/app-handover-guide Hand an app to a client on Fly.io: build in their org from day one, or run fly apps move and rebuild Postgres, Upstash Redis, and Tigris by hand. ![Illustration by Annie Ruygt of a house with a Welcome sign and hot air balloons in the backgroud](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/app-handover-guide.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=ccbd50618f9a24f1b723e952ed2f0227) > [!NOTE] > > **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. 2. **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. 3. **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. 4. **They remove you when it’s over.** Or not—you can stick around for maintenance if that’s part of the deal. 5. **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.** 2. **Have the customer invite one of your devs to their org.** This person needs to be in *both* orgs to do the move. 3. **Transfer the app:** Use the CLI command `fly apps move --org `, or go to the App Settings page in your Dashboard, click Move app, pick the new organization, and wait for the deployment to finish. ![App settings page with Move app option](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/move-app-ui-sm.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=394bc6094260df118b073dec6cf66228) 1. **Be aware:** 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. 2. **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) 3. **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. ### 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 * [Move an app between orgs](/apps/move-app-org) * [Fly.io billing](/about/billing) # Build Secrets Source: https://docs.fly.io/apps/build-secrets Use secrets in a Docker build on Fly.io: mount them in a RUN step with --mount=type=secret, pass values with fly deploy --build-secret, and test locally. You can set [secrets](/apps/secrets) for your applications, but these are only available at *run-time*. They aren't available when building your Docker image without a little extra work. To make a secret available at build time, we'll use [Docker secrets](https://docs.docker.com/develop/develop-images/build_enhancements/). It's a 2-step process: 1. Mount a secret into your `Dockerfile` 2. Provide the value for that secret when running `fly deploy` ## Mounting secrets Mount a secret into your `Dockerfile` within a `RUN` statement: ```dockerfile theme={null} # Note: You can mount multiple secrets RUN --mount=type=secret,id=MY_SUPER_SECRET \ MY_SUPER_SECRET="$(cat /run/secrets/MY_SUPER_SECRET)" some_command \ && more_commands_maybe ``` This creates a new file when running `docker build`. Secrets are stored in the `/run/secrets` directory. The file name is the `id` you passed when mounting the secret. The content of that file contains the value of the secret. To mount more than one secret, add a `--mount` line for each: ```dockerfile theme={null} RUN --mount=type=secret,id=SECRET_ONE \ --mount=type=secret,id=SECRET_TWO \ SECRET_ONE="$(cat /run/secrets/SECRET_ONE)" \ SECRET_TWO="$(cat /run/secrets/SECRET_TWO)" some_command ``` The `--mount` directive is not a shell command, so there's no need to add `&&` after it as you commonly see when chaining commands. ## Secret values You need to provide the values of the mounted secrets when running `fly deploy`: ```bash theme={null} fly deploy \ --build-secret MY_SUPER_SECRET=some_value ``` To pass more than one secret, repeat the `--build-secret` flag: ```bash theme={null} fly deploy \ --build-secret SECRET_ONE=value_one \ --build-secret SECRET_TWO=value_two ``` ## Testing build secrets locally If you want to test your Docker build locally (before deploying to Fly.io), the commands to do so would look something like this: ```bash theme={null} echo -n "secret_value" > mysecret.txt docker build --secret id=MY_SUPER_SECRET,src=mysecret.txt . ``` ## Automate the inclusion of build secrets using an ephemeral Machine The above requires you to have access to the values of secrets and to provide those values on the command line. If this poses a problem, an alternative may be to create an ephemeral Machine using the `fly console` command to do the deployment. An example Dockerfile you can use for that purpose: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM flyio/flyctl:latest as flyio FROM debian:bullseye-slim RUN apt-get update; apt-get install -y ca-certificates jq COPY <<"EOF" /srv/deploy.sh #!/bin/bash deploy=(flyctl deploy) touch /srv/.secrets while read -r secret; do echo "export ${secret}=${!secret}" >> /srv/.secrets deploy+=(--build-secret "${secret}=${!secret}") done < <(flyctl secrets list --json | jq -r ".[].name") deploy+=(--build-secret "ALL_SECRETS=$(base64 --wrap=0 /srv/.secrets)") ${deploy[@]} EOF RUN chmod +x /srv/deploy.sh COPY --from=flyio /flyctl /usr/bin WORKDIR /build COPY . . ``` The `deploy.sh` script contained within this Dockerfile will provide each secret individually as well as package up a script to set all secrets at once. You can set all build secrets at once in your Dockerfile using: ```dockerfile theme={null} RUN --mount=type=secret,id=ALL_SECRETS \ eval "$(base64 -d /run/secrets/ALL_SECRETS)" && \ some_command ``` Assuming your builder Dockerfile is named `Dockerfile.builder`, you can launch the ephemeral machine using the following command: ```bash theme={null} flyctl console --dockerfile Dockerfile.builder -C "/srv/deploy.sh" --env=FLY_API_TOKEN=$(fly auth token) ``` # Guidelines for concurrency settings Source: https://docs.fly.io/apps/concurrency Set soft_limit, hard_limit, and connections vs requests concurrency in fly.toml, and see how Fly Proxy uses them for load balancing and autostop. Concurrency settings are used by Fly Proxy for important things like [load balancing](/reference/load-balancing#load-balancing-strategy) and [autostop/autostart](/launch/autostop-autostart) for Machines. The following concurrency settings apply per Machine and per service in your app: `type`: Use `connections` or `requests` as the metric for app load concurrency. `hard_limit`: At or above this number, no new traffic goes to the Machine. `soft_limit`: At or above this number, traffic to the Machine is deprioritized. Traffic is only routed to the Machine if all Machines reach the soft limit. For Fly Launch apps, configure concurrency settings in the [\[http\_service.concurrency\]](/reference/configuration#http_service-concurrency) or [\[services.concurrency\]](/reference/configuration#services-concurrency) sub-sections of your `fly.toml` file. If you're using the Machines API, then concurrency settings are per `service` in [the `config` object](/machines/api/machines-resource#machine-config-object-properties). ## Default settings The default concurrency settings when not specified are a `soft_limit` of 20 and no `hard_limit`. Many apps can handle a few hundred concurrent connections or more. You can try out different concurrency settings and see how it affects performance and load balancing. `connections` is the default concurrency type. ## How Fly Proxy handles concurrency Fly Proxy doesn't consider the exact number of concurrent connections or requests. From the proxy's point of view a Machine is handling between 0 and the soft limit, handling between the soft limit and the hard limit, or is at the hard limit. Along with region, this is how the proxy decides which Machine gets traffic. The proxy's view of a Machine's load is affected when the gap between the soft limit and the hard limit is small and/or the app's concurrent connections or requests oscillate between them more frequently. In that case, the proxy routes according to the settings, but the thresholds change too quickly and requests or connections end up being re-routed. General caveats: * If the limits are too high, then your Machines might not be able to process that much concurrently and your app could crash. * If the limits are too low, then the proxy is artificially limiting what your app can process. * If the soft and hard limit are too close, then there might not be enough “time” for the proxy to decide to load balance and the result could be multiple retries. ## Connections or requests The decision to use `connections` or `requests` for concurrency depends on the type of app and its load. `requests` are HTTP requests and the recommended concurrency type for web services. Using `requests` for concurrency can prevent too many connections to your app and reduce latency; the proxy can temporarily pool and reuse connections. `connections` are TCP connections. Multiple requests can be sent over a connection, so you need to consider how your app handles that. If you use `connections` for web services, then the proxy opens a new connection for each HTTP request, which is why `requests` is a better setting for HTTP apps. ## 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](/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](/blueprints/setting-concurrency-limits). # Delete an app Source: https://docs.fly.io/apps/delete Permanently delete a Fly App with fly apps destroy, removing its Machines, volumes, snapshots, IPs, secrets, and images and ending charges. You can scale an app right down to zero Machines if you like. But if you're done with a Fly App forever, you can delete it using `fly apps destroy `. Once you destroy an app, you can no longer access any part of it, and you'll no longer be charged for any part of it either. This includes Fly Machines, Fly Volumes, IP addresses, secrets, Docker images, and so on. > [!WARNING] > > Warning: When you destroy an app, you delete its volumes and volume snapshots. If you want the data from an app's persistent storage, then get the data before you destroy the app. # Tips to fine-tune your app on Fly.io Source: https://docs.fly.io/apps/fine-tune-apps Benchmark an app on Fly.io: compare edge and app response metrics, check routing with debug.fly.dev, and factor in concurrency and autostop. It’s safe to assume Fly.io can support thousands of requests or connections per second and can keep hundreds of thousands of them live at any given moment. This is even truer when the clients connect from multiple geographic locations. That settled, you can still use the following guidelines to gather data to fine-tune and optimize your app and configuration to run on Fly.io. ## Fly.io metrics We have various ways to access metrics for Fly Proxy, your app, and Fly Machines. Learn more about [Fly Metrics](/monitoring/metrics). * Edge HTTP response time is the total time it takes to respond with the first HTTP response headers. * App HTTP response time is the same, but only considers your app’s response time, not the overhead of routing, load balancing, retries, and so on. CPU and memory resource usage are interesting, but are rarely the culprit unless you have everything else in your benchmark right. The application logic often is often the issue when it comes to excessive CPU and memory usage. ## Routing Check which [Fly.io region](/reference/regions) you're reaching. The region you reach should be the fastest region for you, which may not be the closest geographically. You can use the debug.fly.dev server to check your region: ``` curl debug.fly.dev ``` Use `traceroute` or `mtr` to see the routing to Fly.io. For example: ``` traceroute debug.fly.dev ``` Check the base latency for the region you’re reaching: ``` ping debug.fly.dev ``` > [!NOTE] > > **Important**: Do you have a stable internet connection? `ping` can jitter quite a bit on bad routes. ## Application logic and performance Some common culprits of excessive CPU and memory usage are: * Using a single thread * Blocking * Various misconfigurations, for example, wrong or missing environment variables ### HTTP response times Fly.io HTTP response time metrics measure how long it takes for your app to start responding with headers, not the response body. ### Multithreading If your app has multiple threads, make sure that contention isn't an issue. You can also [scale CPU resources](/launch/scale-machine) as needed. ## Fly.io configuration Some configuration settings that are more likely to affect app performance and benchmarking. ### Machine sizing Right-size Machines to use more parallel processing. Learn more about [Machine sizing](/machines/guides-examples/machine-sizing#general-rules). ### Concurrency settings for connections or requests Refer to our guidelines for [concurrency settings](/apps/concurrency). ### Auto stop and start feature [Auto stop and start](/launch/autostop-autostart) can be turned off if you want to test the performance of your app without that variable. If you don’t turn off auto stop and start, then you’ll likely have some pretty high p99 values due to the initial cost of Fly Proxy queueing connections while waiting for the Machine to start. ## Distributed systems Fly.io has rate limits and hard limits outside of the control of each app; these limits are per edge and per service. If you test from a single location, then you'll hit our rate limits much faster than if you test from multiple locations. Pay attention to how your benchmarking nodes are routing. Inefficient routing adds latency and will skew your results. If incorrect routing is causing issues with your app testing or your production apps, then post in community or contact support (depending on you plan) and we'll look into it with our network providers. ## Challenges with benchmarking You probably won’t be able to realistically benchmark your app for a combination of reasons, including: * Users will have a large variety of ISPs and be routed differently based on ad hoc peering agreements, sometimes in non-optimal ways. * It’s difficult to remove the Anycast routing factor from benchmarking; you have to live with it. In some cases, a better option might be to send part of your real traffic to Fly.io and then compare metrics. # Going to production checklist Source: https://docs.fly.io/apps/going-to-production Production checklist for Fly.io apps: SSO, tokens, secrets, Managed Postgres, Machine sizing, multi-region scaling, custom domains, metrics, CI/CD. Use this checklist to help you set up a production environment on Fly.io. ![Illustration by Annie Ruygt of Frankie the hot air balloon waving to a bird sitting on a hour roof](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/outlook.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=54504c25107c77756a3c3edc85a7332d) ## 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. > [!NOTE] > > **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. ## Security > **Set up single sign-on for organizations** > > Enable SSO on your organization to take advantage of Google or GitHub authentication security. Learn more about [Single sign-on for organizations](/security/sso). > **Isolate staging and production environments** > > Use organizations to limit access to your production environment. Read this guide: [Staging and production isolation](/blueprints/staging-prod-isolation). > **Enforce least privilege access** > > Use access tokens to allow only the minimum access level required by team members to your organization, apps, and Machines. Understand [access tokens](/security/tokens). > **Protect sensitive information** > > Set secrets to store sensitive data and make them available as environment variables to your app. Read about [Secrets and Fly Apps](/apps/secrets). > **Make sure private services are not exposed** > > 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](/flyctl/cmd/fly_ips). Assign private apps a [Flycast address](/networking/flycast) instead. > **Use Arcjet application security for JavaScript apps** > > 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](/security/arcjet). ## Databases > **Use Managed Postgres** > > We recommend using [Fly.io’s Managed Postgres](/postgres), our fully-managed database service that handles all aspects of running production PostgreSQL. > **Practice your disaster recovery plan** > > Practice restoring your managed Postgres database from a backup before you actually need to. You can do this anytime from the Managed Postgres dashboard. ## App performance > **Get Machine sizing right** > > Most conventional production web apps require [performance CPUs](/machines/cpu-performance). Also make sure you have enough RAM for your app and/or enable [swapping to disk](/reference/configuration/#swap_size_mb-option) to deal with brief spikes in memory use. Find out more details in our [Machine sizing guide](/machines/guides-examples/machine-sizing). > **Fine-tune your app** > > Learn about optimizing your app on Fly.io. Read these tips to [fine-tune your app on Fly.io](/apps/fine-tune-apps). ## Availability, resiliency, and costs > **Use multiple Machines for resiliency** > > 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](/blueprints/resilient-apps-multiple-machines). > **Scale your app into more regions** > > Scale your app in multiple regions closest to your app’s users. Find out how to [Scale an app’s regions](/launch/scale-count/#scale-an-apps-regions). > **Use autostop/autostart to reduce costs** > > 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](/launch/autostop-autostart). > **Set up autoscaling by metric to reduce costs** > > 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](/launch/autoscale-by-metric). ## Networking > **Set up a custom domain** > > Configure a certificate for your domain. Learn how to [use a custom domain](/networking/custom-domain). > **Consider using a dedicated IPv4 address** > > Completely eliminate the chance of blacklisted spammers causing problems for your app. There is a small [added cost](/about/pricing/#anycast-ip-addresses) for dedicated IPv4 addresses. Read more about [Dedicated IPv4](/networking/services/#dedicated-ipv4). > **Set up Flycast for private apps** > > 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](/networking/flycast). ## Monitoring > **Monitor your app with fully-managed metrics** > > Use managed Prometheus and managed Grafana dashboards to monitor your app. Read about [Metrics on Fly.io](/monitoring/metrics). > **Use Sentry for error tracking** > > Sentry is an application monitoring platform that helps you identify and fix software problems before they impact your users. [Sign up with Sentry](https://sentry.io/signup/), then set your `SENTRY_DSN` as an app secret. Read more about [Application Monitoring by Sentry](/monitoring/sentry). > **Export your logs** > > Set up the Fly Log Shipper to aggregate your app’s logs to a service of your choice. Read more about [Exporting logs](/monitoring/exporting-logs). ## CI/CD > **Generate review apps with GitHub Actions** > > 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](/blueprints/review-apps-guide). > **Deploy with GitHub Actions** > > 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](/launch/continuous-deployment-with-github-actions). ## Get support # Apps on Fly.io Source: https://docs.fly.io/apps/index Fly Apps docs hub: Fly Launch for deploying and scaling, runtime and build secrets, and production guides on availability, tuning, and concurrency. ![Illustration by Annie Ruygt of a green block with A written on it, followed by a database stack in a dancing move](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/apps-new-and-fun.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=c256f83d2b60ecae18c0dcf26235835f) An app on Fly.io can be anything from a simple frontend web app to a complex arrangement of processes and Machines all doing their own thing. Check out the [Fly Apps overview](/apps/overview) for details. ## Fly Launch *If you're looking to deploy an app on Fly.io, you probably want Fly Launch. Use Fly Launch to create your app and then manage the whole lifecycle, from starting to scaling to changing and redeploying.* * **[Get started](/getting-started):** Launch your own app or try a demo app. * **[Use Fly Launch](/launch/index):** Learn how to create, configure, deploy, and scale your app. *** ## Secrets *Store sensitive data, like usernames and passwords, as secrets in your app. Secrets are made available to the app as environment variables.* * [Set app secrets to be available at runtime](/apps/secrets) * [Mount secrets in your Dockerfile to be available at buildtime](/apps/build-secrets) *** ## Production apps *Get your app ready for production on Fly.io.* * [Going to production checklist](/apps/going-to-production) * [App availability and resiliency](/apps/app-availability) * [Fine-tune your app](/apps/fine-tune-apps) * [Guidelines for concurrency settings](/apps/concurrency) *** ## Related topics * [Databases & Storage](/database-storage-guides) * [Networking](/networking) * [Monitoring](/monitoring) * [Security](/security) * [Blueprints: Fly.io patterns for your projects](/blueprints) # Get information about an app Source: https://docs.fly.io/apps/info Inspect a running Fly App with fly status, fly machine status, fly ips list, fly config show, fly ssh console, and fly logs, with sample output. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-books.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=4d2c8f101f42e1f379c99ac8e1e17195) Once your Fly App is launched, `flyctl` has various tools for getting information about it. You can also find a lot of information on your Fly.io [dashboard](https://fly.io/dashboard). ## Find all your apps You can see a list of all your Fly Apps: ```bash cmd theme={null} fly apps list ``` ```out out theme={null} NAME OWNER STATUS PLATFORM LATEST DEPLOY testrun personal deployed machines 21h17m ago my-app personal suspended machines 2023-11-15T23:33:07Z ``` ## App overviews If you want a brief app overview, including a list of Machines on that app with their current status, use `fly status`: ```bash cmd theme={null} fly status -a testrun ``` ```out out theme={null} App Name = testrun Owner = personal Hostname = testrun.fly.dev Platform = machines ID STATE REGION HEALTH CHECKS IMAGE CREATED UPDATED 06e82d43ad1587 started yyz 1 total, 1 passing testrun:deployment-01GQ0HKV6TDT7DX0G50MT50FZD 2023-01-17T17:54:04Z 2023-01-17T21:42:33Z ``` As with many flyctl commands, if you leave off the `-a` flag, `fly status` will infer the app name from the `fly.toml` file in the working directory, if there is one. `fly machine list` yields a different set of information for each Machine, including the Machine's [internal IPv6 address](/networking/private-networking): ```bash cmd theme={null} fly machine list -a testrun ``` ```out out theme={null} 1 machines have been retrieved from app testrun. View them in the UI here (​https://fly.io/apps/testrun/machines/) testrun ID NAME STATE REGION IMAGE IP ADDRESS VOLUME CREATED LAST UPDATED APP PLATFORM PROCESS GROUP 178115db494e18 holy-waterfall-9884 started lhr testrun:deployment-01GTYFV11PM7D7B30AWZSH1FZE fdaa:0:3b99:a7b:a98:6c48:67c5:2 2023-03-07T16:52:59Z 2023-03-07T16:53:06Z v2 app ``` ## Examine a specific Machine You can drill down and get an overview of a particular Machine with `fly machine status`. This is where you'll find the Machine's CPU size and RAM settings. ```bash cmd theme={null} fly machine status e286065f969386 ``` ```out out theme={null} Machine ID: e286065f969386 Instance ID: 01GQR4GR5TPQC65T5RMKR0HS87 State: started Machine ID = e286065f969386 Instance ID = 01GQR4GR5TPQC65T5RMKR0HS87 State = started Image = testrun:deployment-01GQR4GN7EA8G12VE1DBRQG4KQ Name = rough-tree-1360 Private IP = fdaa:0:3b99:a7b:8aeb:fea3:148b:2 Region = iad Process Group = Memory = 256 CPUs = 1 Created = 2023-01-25T21:35:32Z Updated = 2023-01-26T22:52:36Z Command = Event Logs STATE EVENT SOURCE TIMESTAMP INFO started start flyd 2023-01-26T17:52:36.678-05:00 created launch user 2023-01-26T17:52:33.348-05:00 ``` ## Services See the configured services with `fly services list`. ```bash cmd theme={null} fly services list ``` ```out out theme={null} Services PROTOCOL PORTS FORCE HTTPS TCP 80 => 8080 [HTTP] True 443 => 8080 [TLS,HTTP] False ``` ## Public IP addresses Find your app's public Anycast IPs and private Flycast IPs with `fly ips list`. ```bash cmd theme={null} fly ips list ``` ```out out theme={null} VERSION IP TYPE REGION CREATED AT v6 2a09:8280:1::2d:678b public (dedicated) global Sep 1 2023 19:47 v6 fdaa:2:45b:0:1::23 private global Mar 16 2024 18:20 v4 66.241.124.63 public (shared) Jan 1 0001 00:00 ``` Read more about [Public networking](/networking/services) and [Private networking](/networking/private-networking). ## Check on it from inside You can use `fly ssh console` to get a prompt on a Machine in your app (as long as the Machine's Docker image includes `sh`). You may want to connect to a specific Machine, and you can do that with `fly ssh console -s`: ```bash cmd theme={null} fly ssh console -s ``` ```out out theme={null} ? Select Machine: iad: e286065f969386 fdaa:0:3b99:a7b:8aeb:fea3:148b:2 rough-tree-1360 Connecting to fdaa:0:3b99:a7b:8aeb:fea3:148b:2... complete # ``` If you have a particular command in mind, it might be quicker to use the `-C` flag to just run the command; for example to check on free space in the Machine file system, you might run ```bash cmd theme={null} fly ssh console -C df ``` ```out out theme={null} Connecting to fdaa:0:3b99:a7b:7e:3155:9844:2... complete Filesystem 1K-blocks Used Available Use% Mounted on devtmpfs 103068 0 103068 0% /dev /dev/vda 8191416 172748 7582856 3% / shm 113224 0 113224 0% /dev/shm tmpfs 113224 0 113224 0% /sys/fs/cgroup /dev/vdb 1011672 2564 940500 1% /storage ``` > [!NOTE] > > If you are running an interactive command (like a shell, IEx, Django management command, or the Rails console), you may need to use the `--pty` flag. This tells the SSH server to run the command in a pseudo-terminal. (If you're familiar with OpenSSH, this is like the `-t` flag for `ssh`.) ## Inspect the current configuration of a deployed app or Machine Machines can be configured individually, but Fly Launch applies the app's config on `fly deploy` to all Machines that are administered by the app. Display the app configuration in JSON format with `fly config show`. ### Show the app config ```bash theme={null} fly config show -a testrun ``` ### Show a Machine's configuration Tack a specific Machine's current configuration onto the output of `fly machine status` by appending the `-d` flag. Machines belonging to a Fly App will *usually* be managed by `fly deploy` and so have the same configuration as their app. ```bash cmd theme={null} fly m status e784459b655483 -d ``` ```out out theme={null} Machine ID: e784459b655483 Instance ID: 01GQTQV0EMGV8XY4NYJN68247W State: started VM ID = e784459b655483 Instance ID = 01GQTQV0EMGV8XY4NYJN68247W State = started Image = testrun:deployment-01GQTQTJB5RCS03FKGX9K2H60B Name = red-feather-7761 Private IP = fdaa:0:3b99:a7b:92:9336:667f:2 Region = iad Process Group = Memory = 256 CPUs = 1 Created = 2023-01-27T23:08:41Z Updated = 2023-01-27T23:09:10Z Command = Event Logs STATE EVENT SOURCE TIMESTAMP INFO started start flyd 2023-01-27T18:09:10.855-05:00 created launch user 2023-01-27T18:08:41.361-05:00 Config: { "init": {}, "image": "registry.fly.io/testrun:deployment-01GQTQTJB5RCS03FKGX9K2H60B", "metadata": { "fly_platform_version": "v2", "fly_release_id": "QgDeJ3o11V2D7fkXVQGvPBON", "fly_release_version": "1" }, "restart": {}, "services": [ { "protocol": "tcp", "internal_port": 4999, "ports": [ { "port": 80, "handlers": [ "http" ], "force_https": true }, { "port": 443, "handlers": [ "tls", "http" ] } ], "checks": [ { "type": "tcp", "interval": "15s", "timeout": "2s" } ], "concurrency": { "type": "connections", "hard_limit": 25, "soft_limit": 20 } } ], "guest": { "cpu_kind": "shared", "cpus": 1, "memory_mb": 256 } } ``` ## Watch an app's logs Running `fly logs` displays an app's logs as they happen. Logs include the console output of all instances of an application. Here are logs for an app that hangs around doing nothing but has a volume mounted so you can ssh in and create files. ```bash cmd theme={null} fly logs -a testrun ``` ```out out theme={null} 2023-03-07T16:17:48Z runner[5683606c41098e] lhr [info]Pulling container image 2023-03-07T16:17:51Z runner[5683606c41098e] lhr [info]Unpacking image 2023-03-07T16:18:00Z runner[5683606c41098e] lhr [info]Setting up volume 'data' 2023-03-07T16:18:00Z runner[5683606c41098e] lhr [info]Uninitialized volume 'data', initializing... 2023-03-07T16:18:00Z runner[5683606c41098e] lhr [info]Encrypting volume 2023-03-07T16:18:05Z runner[5683606c41098e] lhr [info]Opening encrypted volume 2023-03-07T16:18:07Z runner[5683606c41098e] lhr [info]Formatting volume 2023-03-07T16:18:08Z runner[5683606c41098e] lhr [info]Configuring firecracker 2023-03-07T16:18:08Z app[5683606c41098e] lhr [info]Starting init (commit: 08b4c2b)... 2023-03-07T16:18:08Z app[5683606c41098e] lhr [info]Mounting /dev/vdb at /storage w/ uid: 0, gid: 0 and chmod 0755 2023-03-07T16:18:08Z app[5683606c41098e] lhr [info]Preparing to run: `sleep infinity` as root 2023-03-07T16:18:08Z app[5683606c41098e] lhr [info]2023/03/07 16:18:08 listening on [fdaa:0:3b99:a7b:7e:3155:9844:2]:22 (DNS: [fdaa::3]:53) ``` `fly logs` stays open, watching the logs, until you stop it (ctrl-C). Learn more about [logging on Fly.io](/monitoring/logging-overview). # Move an app between organizations Source: https://docs.fly.io/apps/move-app-org Move an app to another Fly.io org with fly apps move or the dashboard. Expect brief downtime and reconfigure Upstash Redis, Tigris, and Postgres. ![Illustration by Annie Ruygt of a figure jumping from one mountian to another](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/migrating-from-aws.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=805c99c4914a71cef1ff3c36c73019bb) 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 in `flyctl`, or using the App Settings page of the Dashboard. You'll need to be a member of both organizations to move an app. **Move an app from one organization to another via the CLI:** ```bash theme={null} fly apps move --org ``` > [!NOTE] > > **Fly Postgres:** You can't use the `fly apps move` command for Fly Postgres apps. To move a Postgres app to another organization, create a new Postgres app under the target organization, and then [restore the data from your current Postgres app volume snapshot](/unmanaged-postgres/managing/backup-and-restore#restoring-from-a-snapshot). **Move an app from one organization to another via the App Settings page:** Go to the App Settings page in your Dashboard, click Move app, pick the new organization, and wait for the deployment to finish. Your app will be up and running once the transfer is complete. ![App settings page with Move app option](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/move-app-ui-sm.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=394bc6094260df118b073dec6cf66228) ## App downtime Your app will have up to a few minutes of downtime while the move operation completes. ## Resources moved with the app The following app resources are transferred to the new org automatically: * Fly Machines and Fly Volumes (including data) * environment variables and secrets * certificates and domain names * LiteFS databases (when `$FLY_APP_NAME` is used for your Consul key in `litefs.yml`) ## Resources that you need to manually reconfigure The following extension services need to be reconfigured for the app after the move: * **[Upstash for Redis](/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](/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 guide: considerations and approaches](/apps/app-handover-guide) # Fly Apps overview Source: https://docs.fly.io/apps/overview See what makes up a Fly App on Fly.io, a group of Machines with config, IPs, certificates, secrets and volumes, and the ways to create one. A Fly App is an abstraction for a group of Fly Machines running your code on Fly.io. You can create and manage your app as a whole using [Fly Launch](/launch/index), but you can also have a Fly App with individual Machines running tasks or user code. For a quick primer on Fly Launch, Fly Machines, and Fly Apps, see [Fly.io essentials](/getting-started/essentials). ## A Fly App can be almost anything From an admin point of view, a Fly App is just a group of Machines (with optional attached volumes) that belongs to one organization. From a developer point of view, a Fly App might be: * a fullstack application (or just part of one) * a database * a few Machines running tasks, or a bunch of Machines, all with different configs, doing things you want them to do * anything you can think of doing with fast-launching Machines All the apps in your organization can communicate over a [private network](/networking/private-networking), so it’s also possible to have multiple apps working together as one system. ## Components of a Fly App Fly Apps include: * app configuration * provisioned resources * Anycast IP addresses * certificates * custom domains * secrets * Fly Volumes (optional) Fly Apps run on flyd. Learn more about how flyd works and how it came to be in the blog post: [Carving the Scheduler Out of Our Orchestrator](https://fly.io/blog/carving-the-scheduler-out-of-our-orchestrator/). ## Ways to create an app There might be an infinite number of potential apps you can run on Fly.io, but there are only a few ways to create one. ### Create and deploy with Fly Launch Fly Launch is perfect for most apps and databases. Use Fly Launch to create your app and then manage the whole lifecycle, from starting to scaling to changing and redeploying. [Get started](/getting-started) with Fly Launch or learn more about using [Fly Launch](/launch/index) to create, configure, deploy, and scale your app. ### Create an app manually Most of the time, all you need is [Fly Launch](/launch/index) to create and manage your app. But you can skip all the handy launch scanners and resource provisioning Fly Launch offers and use the `fly apps create` command or the Machines API to create an app and then piece it together. You can get a [`fly.toml` config file](/reference/configuration) from an example app or hand-craft one yourself. Or if you don't need app-wide config, you can use the Fly App to hold the Machines you plan to spin up individually based on a particular image. Using the `fly apps create` command is useful when deploying our [autoscaler app](/launch/autoscale-by-metric), or example apps created by others, that already have `fly.toml` and Dockerfiles ready to go. Creating apps with the Machines API might be the right choice if you want to create apps for your own users with pre-defined or custom `fly.toml` files. For more information, see the [`fly apps create` docs](/flyctl/cmd/fly_apps_create) and the [create App resource docs](/machines/api/apps-resource#create-a-fly-app). > [!NOTE] > > **Tip:** When you [create a Machine with `fly machine run`](/machines/flyctl/fly-machine-run) without specifying an app to put it in, you automatically get a new app at the same time. This scenario might be useful when you're spinning up individual Machines on-demand for multiple users or tasks. # Restart apps or Machines Source: https://docs.fly.io/apps/restart Restart all Fly Launch Machines in an app with fly apps restart, or reboot specific Machines by ID with fly machine restart to reset the root filesystem. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-mountains.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=882985b0b822f950c9f5a1a84631d5b7) Running `fly deploy` on an app creates a new release of the app, and updates and restarts the Machines that belong to Fly Launch. Sometimes you don't want to update anything; you just want to reboot and start the root file system afresh. Restarting wipes the ephemeral file system, just as a `fly deploy` or `fly machine update` will. ## Restart every Machine managed by Fly Launch `fly apps restart ` restarts all Machines in the app that are managed by Fly Launch. In other words, all the Machines deployed with `fly deploy` and configured with `fly.toml`. ## Restart individual Machines If one particular Machine needs a kick—or you want to restart an unmanaged Machine—then run `fly machine restart`: ```bash theme={null} fly machine restart ``` You can provide multiple ``s to restart several Machines in one command. # Secrets and Fly Apps Source: https://docs.fly.io/apps/secrets Set, stage, list and unset app secrets on Fly.io with fly secrets, see how the encrypted vault injects them as env variables, and mount secrets as files. ## 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. If you need secrets to be available when building your Docker image, see [Build secrets](/apps/build-secrets). ## 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. 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] > > **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. ## 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. The following example sets a secret that's available as the `DATABASE_URL` environment variable within your application processes: ```sh theme={null} fly secrets set DATABASE_URL=postgres://example.com/mydb ``` To set, or update, a secret in the app's vault, but defer updating the Machines to later, use the `--stage` option. For example: ```bash theme={null} 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](/flyctl/cmd/fly_secrets) or run `fly secrets --help`. > [!NOTE] > > **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 that are set for your app. The list shows only the secret name; the value is not shown as it is a secret. For example: ```bash cmd theme={null} fly secrets list ``` ```output output theme={null} NAME | DIGEST | DATE +--------------+----------------------------------+---------+ MY_SECRET | b9e37b7b239ee4aefc75352fe3fa6dc6 | 17s ago DATABASE_URL | cdbe3268a82bfe993921b9cae2a526af | 17s ago ``` For security reasons, we do not allow read access to the plain-text values of secrets. ### Remove secrets Remove one or more secret values from your app by name. The following example removes two secrets from the app: ```bash theme={null} 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: ```bash theme={null} 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 theme={null} [[files]] guest_path = "/path/to/secret.txt" secret_name = "SUPER_SECRET" ``` You can use this with other `[[files]]` entries, check the `fly.toml` [reference](/reference/configuration#the-files-section) for more details. # Troubleshoot apps when a host is unavailable Source: https://docs.fly.io/apps/trouble-host-unavailable Recover apps on Fly.io when a host goes down: scale to zero and back up, redeploy, restore a volume from a snapshot, or rebuild a single-node Postgres app. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-magnify.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=0004b6ad2a7734d735cc510f3beae622) Fly.io has multiple hosts (physical hardware) in every supported [region](/reference/regions). From time to time, a hardware failure, connectivity issues, or other factors will make a host unavailable. A single-host issue may cause a sustained or intermittent outage, or generally degraded performance, and this will disproportionately affect apps that run on a single Machine. A host can be down for an hour, or a day, or sometimes longer. For production apps, or any apps that require high availability, we strongly recommend redundancy to protect against single-host failure. This means running multiple Machines, and for stateful apps it means having a clustering mechanism (such as those built into LiteFS and Postgres, for example) to replicate data between multiple volumes, because volumes are pinned to specific hosts. > [!NOTE] > > **Important:** The steps for recovery in this document don’t guarantee that you’ll be able to access your app if you have a single Machine on a host that’s affected by an ongoing issue. ## Notification of host issues You’ll get a notification on the Status page in your [dashboard](https://fly.io/dashboard/) when there’s a host issue that affects one or more of your apps. We don’t post individual host issues to our main status page because a host issue usually affects a small number of users in a single region. ## Apps with one Machine and no volumes You can usually restore an app with one Machine and no volumes by scaling down and back up, or by deploying a new version of the app. ### Scale the app to get new Machines For apps without volumes, you can bring up a new Machine by scaling to zero and then back up again. The new Machines will be placed on a healthy host. 1. Scale the app to zero Machines: ```bash theme={null} fly scale count 0 ``` 2. Scale back up to 2 or more Machines: ```bash theme={null} fly scale count 2 ``` > [!NOTE] > > **Note:** When you scale all your app's Machines to zero, the Machine size resets to shared-cpu-1x with 256MB of RAM. Use one of the `--vm-` options to size the Machines. For example: `fly scale count 2 --vm-size shared-cpu-4x`. See the [fly scale count docs](/flyctl/cmd/fly_scale_count) for options. ### Deploy the app to get new Machines In most cases, deploying a new version of your app with `fly deploy` should also work for an app without volumes, because the new Machines will be deployed to healthy hosts. > [!NOTE] > > **Important:** Running `fly deploy` during a host outage generally won't destroy the existing Machine. When the impacted host returns, the Machine that was on it may return as well. In the case of deploying during an outage, this can result in one Machine running an older version of your code. In that case you should destroy the outdated Machine if it comes back. ## Apps with one Machine and an attached volume Volumes are pinned to physical hosts, so when there's a host outage the volume is unreachable. In most cases, you should be able to get your app running again by restoring from a snapshot into a new volume. > [!WARNING] > > **Warning:** We take snapshots once every 24 hours. Any data stored between the time when the snapshot was taken and the time when the restore is made will not be included. 1. Run `fly volumes list` and copy the volume ID. 2. Run `fly volumes snapshots list ` to list the volume’s snapshots and then copy the snapshot ID. 3. Run `fly volumes create --snapshot-id -s ` to create a new volume from a stored snapshot. 4. Run `fly scale count 1` to create a new Machine that will pick up the new volume. ## Fly Postgres database apps If you're running a high availability Postgres cluster with multiple nodes, a host issue impacting one of your nodes shouldn't cause an issue; by default we run each node on a separate host. If the host your primary node is on goes down, the cluster will fail over to a healthy node. However, if your database is running on a single Machine, and you don’t have any replicas to fail over to, then you won’t be able to connect during the host outage. Similar to the single volume steps above, you can create a new Postgres app from your most recent volume snapshot using `fly postgres create --snapshot-id --image-ref `. See [Backup, restores, & snapshots](/unmanaged-postgres/managing/backup-and-restore) for details. ## Prevent downtime when there's a single host issue Fly.io strongly recommends running at least two Machines per app in your primary region and we have features that can help make [app availability and resiliency](/apps/app-availability) more affordable. When possible, we place volumes on different hosts in the primary region. If one host goes down, then your app will still have a Machine and volume on a healthy host. ## Related topics * [App availability and resiliency](/apps/app-availability) * [Resilient apps use multiple Machines](/blueprints/resilient-apps-multiple-machines) * [Troubleshooting your deployment](/getting-started/troubleshooting) * [Fly.io error codes and troubleshooting](/monitoring/error-codes) # Autoscale Fly Machines with autostop or fly-autoscaler Source: https://docs.fly.io/blueprints/autoscale-machines Pair fly-autoscaler with Fly Proxy autostop/autostart to keep a fixed number of stopped Machines ready to start as traffic rises and falls through the day. ![Illustration by Annie Ruygt of two machines dancing and one standing still](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/autoscale-machines.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=f6ef11be5bb7020a307d2ea6b673f9dd) ## Overview You have an app with services that's configured to [automatically start and stop Machines based on traffic demand](/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 guide will take you through the process of configuring the [`fly-autoscaler` app](/launch/autoscale-by-metric) in conjunction with [Fly Proxy autostop/autostart](/launch/autostop-autostart) to always keep a fixed number of Machines ready to be quickly started by Fly Proxy. ## Configure autostop/autostart First, if you haven't already done so, configure the app to allow Fly Proxy to automatically start and stop or suspend Machines based on traffic demand. The autostop/autostart settings apply per service, so you set them within the `[[services]]` or `[http_service]` sections of `fly.toml`: ```toml theme={null} ... [[services]] ... auto_stop_machines = "stop" auto_start_machines = true min_machines_running = 0 ... ``` With these settings Fly Proxy will start an additional Machine if all the running Machines are above their concurrency `soft_limit` and stop running Machines when the traffic decreases. You can set Machines to `"suspend"` rather than `"stop"`, for even faster start-up, but with some [limitations on the type of Machine](https://community.fly.io/t/new-feature-in-preview-suspend-resume-for-machines/20672#current-limitations-and-caveats-8). In the next section you'll configure and deploy `fly-autoscaler` to ensure that the app always has a spare stopped Machine for Fly Proxy to start. ## Configuring and deploying fly-autoscaler `fly-autoscaler` is a metrics-based autoscaler that scales an app’s Machines based on any metric. You can configure it to ensure that there is always additional Machine available for Fly Proxy to start if the traffic increases. First, create a new Fly.io app that will run the autoscaler. ``` $ fly apps create my-autoscaler ``` Create a deploy token so that the autoscaler app has permissions to scale your target app up and down: ``` $ fly tokens create deploy -a my-target-app $ 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 $ fly secrets set -a my-autoscaler --stage FAS_PROMETHEUS_TOKEN="FlyV1 ..." ``` Configure your autoscaler `fly.toml` like this: ```toml theme={null} app = "my-autoscaler" [build] image = "flyio/fly-autoscaler:0.3.1" [env] FAS_PROMETHEUS_ADDRESS = "https://api.fly.io/prometheus/my-org" FAS_PROMETHEUS_METRIC_NAME = "running_machines" FAS_PROMETHEUS_QUERY = "count(fly_instance_up{app='$APP_NAME'})" FAS_APP_NAME = "my-target-app" FAS_CREATED_MACHINE_COUNT = "min(running_machines + 1, 10)" FAS_INITIAL_MACHINE_STATE = "stopped" [metrics] port = 9090 path = "/metrics" ``` With this configuration, the autoscaler will create a new stopped Machine as soon as all available Machines are running (but never more than 10), and will destroy extra stopped Machines if more than one Machine is stopped. Make sure you are using autoscaler version 0.3.1 or newer for `FAS_INITIAL_MACHINE_STATE` configuration option to work. And finally, deploy the autoscaler, using the `--ha` option to deploy only one Machine: ``` $ fly deploy --ha=false ``` ## Related reading * [Autoscale based on metrics](/launch/autoscale-by-metric) * [Autostop/autostart Machines](/launch/autostop-autostart) * [Autostop/autostart private apps](/blueprints/autostart-internal-apps) # Autostart and autostop private apps with Flycast Source: https://docs.fly.io/blueprints/autostart-internal-apps Let Fly Proxy start and stop Machines for a private app on demand by giving it a Flycast address instead of public IPs, for new apps or existing ones. ![Illustration by Annie Ruygt of Frankie the balloon having a picnic with an app and eating strawberries](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/autostart-internal-apps.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=10945d5b96e9e4f4a68c3dbc7f9f83d3) ## Overview You have a private, or internal, app that communicates only with other apps on your [private network](/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 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](/reference/fly-proxy) like geographically aware load balancing. Learn more about [Flycast](/networking/flycast). ## Create a new private app with a Flycast address When you run `fly launch` to create a new app, it automatically assigns your app a public IPv6 address and a shared public IPv4 address. If you know your app won't need to be reachable from the Internet, you can inform Fly Launch to assign a Flycast private IPv6 address instead: ``` fly launch --flycast ``` Next steps: [Configure and deploy a private app](#configure-and-deploy-a-private-app). ## Use Flycast for an existing app If you already have an app and you want to make it private and use Flycast, it's important to make sure you remove the app's public IP addresses. ### Add a Flycast address ``` fly ips allocate-v6 --private ``` ### Remove public IP addresses List your IPs to check whether your app has public IP addresses: ``` fly ips list ``` Example output: ``` VERSION IP TYPE REGION CREATED AT v6 2a09:8280:1::2d:1111 public (dedicated) global Sep 1 2023 19:47 v6 fdaa:2:45b:0:1::11 private global Mar 16 2024 18:20 v4 66.241.124.11 public (shared) Jan 1 0001 00:00 ``` This example app has public IPv4 and IPv6 addresses. These public addresses are automatically assigned to an app with services on the first deploy. Copy the public IP addresses and run the `fly ips release` command to remove them from your app: ``` fly ips release ... ``` For example: ``` fly ips release 2a09:8280:1::2d:1111 66.241.124.11 ``` Next steps: [Configure and deploy a private app](#configure-and-deploy-a-private-app) below. ## Configure and deploy a private app Whether you're creating a new app or making an existing app private, there are a few things you'll need to check or configure. ### Add services in your `fly.toml` config file If your app was private, you might not have configured an `[http_services]` or `[services]` section in `fly.toml` because you didn't want it reachable through the public Internet. Now that you removed the public IPs, you can safely add services to allow access to the app on your private network and enable Fly Proxy to control Machines and load balance traffic. Here's an example `fly.toml` snippet: ```toml theme={null} [http_service] # the port on which your app receives requests over the 6PN internal_port = 8081 # must be false - Flycast is http-only force_https = false # Fly Proxy stops Machines based on traffic auto_stop_machines = "stop" # Fly Proxy starts Machines based on traffic auto_start_machines = true # Number of Machines to keep running in primary region min_machines_running = 0 [http_service.concurrency] type = "requests" # Fly Proxy uses this limit to determine Machine excess capacity soft_limit = 250 ``` > [!NOTE] > > **Important:** Set `force_https = false`; Flycast only works over HTTP. HTTPS isn't necessary because all your private network traffic goes through encrypted WireGuard tunnels. Learn more about [app configuration](/reference/configuration) and [Fly Proxy autostop/autostart](/launch/autostop-autostart). ### Make sure your app binds to `0.0.0.0:` To be reachable by Fly Proxy, an app needs to listen on `0.0.0.0` and bind to the `internal_port` defined in the `fly.toml` configuration file. ### Deploy the app Run `fly deploy` for the configuration changes to take effect. Other apps in your organization can now reach your private app using the [Flycast](/networking/flycast) IP address or the `.flycast` domain. # Connect AWS and other clouds with WireGuard Source: https://docs.fly.io/blueprints/bridge-deployments-wireguard Join a server on AWS or another cloud to your Fly.io private network as a site-to-site WireGuard peer, so Machines and outside services talk privately. ![Illustration by Annie Ruygt of a figure with wings and a ship some balloons and planes](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/bridge-deployment.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=242e0ce4c555c060f26fb08b295b0f07) ## 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](/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. Fly.io private networks use [WireGuard](https://www.wireguard.com/) internally, and when you connect other computers to that network, you do connect to them over WireGuard. WireGuard is used by many people for many reasons, but the primary use is a "site-to-site" VPN like this. This won't route all of your traffic through Fly.io's gateway servers, but it will give you internal access to your private network from anywhere. ## Weaving the networks together Make sure you have [WireGuard](https://www.wireguard.com/install/) installed on your target server. Their [install page](https://www.wireguard.com/install/) has more details, but on an Ubuntu server all you need to do is: ```docker theme={null} sudo apt -y install wireguard ``` 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](/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. For example, I'm going to create a peer in my personal organization for my server named `phantoon`. I live in Ottawa, so the closest datacenter to me is `yyz`. The command to create my peer will look like this: ```docker theme={null} fly wireguard create personal yyz phantoon ~/fly0.conf ``` The `fly0.conf` file contains all the configuration WireGuard needs to set up a tunnel, namely: * The private key for the node * The local IPv6 address the node should use * The DNS server for your private network * Addresses, IP ranges, and public keys for the WireGuard gateway Copy `fly0.conf` to `/etc/wireguard` on the target computer: ```docker theme={null} scp ~/fly0.conf root@phantoon.local:/etc/wireguard/fly0.conf ``` Then connect to the computer (likely over SSH) and enable WireGuard to start on boot: ```docker theme={null} ssh root@phantoon.local systemctl enable --now wg-quick@fly0.service ``` This will create an interface named `fly0`: ```docker theme={null} $ ip addr show fly0 4: fly0: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 link/none inet6 fdaa:0:641b:a7b:9285:0:a:1b02/120 scope global valid_lft forever preferred_lft forever ``` Now try to ping the Machines API server `_api.internal`: ```docker theme={null} $ ping _api.internal -c 4 PING _api.internal (fdaa:0:641b::3) 56 data bytes 64 bytes from fdaa:0:641b::3: icmp_seq=1 ttl=64 time=10.4 ms 64 bytes from fdaa:0:641b::3: icmp_seq=2 ttl=64 time=39.2 ms 64 bytes from fdaa:0:641b::3: icmp_seq=3 ttl=64 time=83.7 ms 64 bytes from fdaa:0:641b::3: icmp_seq=4 ttl=64 time=123 ms ``` This means that you can successfully query any [`.internal` addresses](/networking/private-networking#fly-io-internal-dns) for apps and Machines in your organization. Congrats, you're in! ## Accessing workloads outside of your private network To access a workload outside of your private network (such as a secret store), use the DNS name `peername._peer.internal`. For example, to access the node `phantoon` from your Fly apps, connect to `phantoon._peer.internal`: ```docker theme={null} $ ping phantoon._peer.internal PING phantoon._peer.internal (fdaa:0:641b:a7b:9285:0:a:1b02) 56 data bytes 64 bytes from phantoon._peer.internal (fdaa:0:641b:a7b:9285:0:a:1b02): icmp_seq=1 ttl=62 time=24.4 ms 64 bytes from phantoon._peer.internal (fdaa:0:641b:a7b:9285:0:a:1b02): icmp_seq=2 ttl=62 time=37.6 ms 64 bytes from phantoon._peer.internal (fdaa:0:641b:a7b:9285:0:a:1b02): icmp_seq=3 ttl=62 time=85.4 ms 64 bytes from phantoon._peer.internal (fdaa:0:641b:a7b:9285:0:a:1b02): icmp_seq=4 ttl=62 time=197 ms ``` ## Homework Getting ping working should be good enough to get you started, but here's some optional homework in case you want to see what you can really do with this: * Connect your laptop/workstation to your private network with the same flow (you may need to install the GUI WireGuard app if you use Windows or macOS). * Expose Prometheus metrics on port 9195 of your app and then grab the current metrics with `curl yourapp.internal:9195/metrics` on your laptop. * Install Postgres on your laptop somehow. Configure your app to connect to that Postgres database on your laptop over WireGuard. * 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](/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](/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](/networking/custom-private-networks) How to isolate tenants, apps, or services by creating separate private networks inside your organization — relevant when bridging external infrastructure. # Cell-based architecture Source: https://docs.fly.io/blueprints/cell-based Give each user of your app a dedicated Fly Machine that stops when idle and restarts on access, routed with fly-replay and fly-force-instance-id. Previously know as [shared-nothing-architecture](https://en.wikipedia.org/wiki/Shared-nothing_architecture), the concept was rediscovered and rebranded as [cell-based](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html) by Amazon. This blueprint shows you how to implement a scenario where each user of your application is assigned a new Machine. These Machines, by default, will automatically stop when not in use and be restarted when accessed, so the primary expense will be for volumes and actual usage. > [!WARNING] > > **Warning**: if you follow this example, applications will be deployed without > their databases being replicated. This exposes your application to volume and > Machine failures. Backups become your responsibility. Some approaches to > addressing this requirement are listed at the [bottom of this > page](#backups). The example given here will be based on Rails, but the approach applies to all frameworks. ## Start With a Single Machine Application If you don't have one, the following will do: ``` rails new blog --css tailwind cd blog bin/rails generate scaffold Post title:string body:text fly launch --name blog-$USER-$RANDOM ``` Feel free to substitute a different name for your application. ## Configure Your Routes Modify `config/routes.rb`. Wrap your routes with: ```ruby theme={null} scope ENV.fetch("FLY_MACHINE_ID", "") do ... end ``` If present, move `get "up"` line outside of this block. Uncomment the `root` line. Your final result will look something like this: ```ruby theme={null} Rails.application.routes.draw do scope ENV.fetch("FLY_MACHINE_ID", "") do resources :posts # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") root "posts#index" end # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check end ``` What this will do is place your entire application, minus the health check endpoint, into a namespace when deployed to fly.io. Access to your application will need to be prefixed by your Machine id. When run in environments where the `FLY_MACHINE_ID` environment variable is not set, no prefix is required. You can get a list of Machine ids using: ``` fly machines list -q ``` ## Starting a Second Machine In order to prepare for a multiple Machine deployment, we are going to need to ensure that each request is routed to the correct Machine. We can accomplish this via middleware that makes use of the [fly-replay](/networking/dynamic-request-routing#how-fly-replay-works) response header. Place the following into `config/initializers/fly_router.rb`: ```ruby theme={null} if ENV["FLY_MACHINE_ID"] class FlyInstanceRouter def initialize(app) @app = app end def call(env) segments = env["PATH_INFO"].split('/') instance = segments[1] if instance =~ /^[0-9a-f]{14}$/ and instance != ENV["FLY_MACHINE_ID"] return [409, {"Fly-Replay" => "instance=#{instance}"}, [""]] end @app.call(env) end end Rails.application.config.middleware.use(FlyInstanceRouter) end ``` Deploy this change using `fly deploy`, then use the following command to create a second Machine: ``` fly machine clone ``` At this point, you have two instances of the blog application, each on its own Machine, with its own volume and sqlite3 database. Note that [fly machine clone](/flyctl/cmd/fly_machine_clone) has a number of options that can be used to specify such things as the region, vm sizes, and whether the volume requires a unique zone. Regions, in particular, can enable you to put both applications *and* data close to users. ## Optimizing your routes This section is optional, and depends on a unique feature of Rails' [Hotwire](https://hotwired.dev/) implementation. At this point, many requests will end up making two hops: first to a nearby Machine then to the desired Machine. This increases latency of responses, particularly when it is necessary to wake two Machines to process the first request. Not much can be done to avoid this for the first request, but subsequent requests can route to the correct Machine using the `fly-force-instance-id` request header. Place the following into `app/javascript/controllers/fly_router_controller.js`: ```js theme={null} import { Controller } from "@hotwired/stimulus" // Connects to data-controller="fly-router" export default class extends Controller { connect() { console.log('fly router connected') this.instance = this.element.dataset.instance; document.documentElement.addEventListener( 'turbo:before-fetch-request', this.beforeFetchRequest ) } disconnect() { document.documentElement.removeEventListener( 'turbo:before-fetch-request', this.beforeFetchRequest ) } beforeFetchRequest = event => { console.log('injecting ' + this.instance); event.detail.fetchOptions.headers['fly-force-instance-id'] = this.instance; } } ``` Finally, modify the `` line in `app/views/layouts/application.html.erb`: ```erb theme={null} data-instance="<%= ENV['FLY_MACHINE_ID'] %>" data-controller="fly-router"<% end %>>> ``` Deploy this change using `fly deploy`. ## Next steps The above merely amounts to a proof of concept. A production application will likely make use of one or more of the following: * While putting the Machine id into the URL path is effective, it isn't very ergonomic. A better solution would be have a registry of paths and their associated Machines. * Implementing routing in middleware gets the job done, but separating this out to a separate process using an application like [`nginx`](https://nginx.org/en/) can have a number of advantages: * `fly-replay` is limited to request payloads of one megabyte or less, which would affect features like file uploads. A [reverse proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) might be a better solution. * support for custom subdomains as an alternative to path namespaces and scopes. * authentication and serving static assets can be performed outside of your application. * Add-ons like [Phusion Passenger](https://www.phusionpassenger.com/) can enable you to run multiple instances of your application in one Machine. * Monitoring becomes more crucial when you have hundreds or even dozens of independent Machines. Fly.io will combine your logs and while this addresses many problems, it doesn't help deal with logs that are missing due to application or network problems. Consider having applications emit a heartbeat, and write monitoring software that looks for missing heartbeats and reports them as issues. * While applying updates when all of the services for a single Machine are self contained is an easier problem then upgrading potentially interdependent services running live in production, it does come at a cost: a blue/green deployment is not possible so a focus on reducing startup times is necessary. * Consider building an administrative web based interface for your application. User registration and Machine assignments are likely coupled in your workflow. While Fly.io provides a [Machines API](/machines/api), you can go a long way with old-fashioned scripting using the [flyctl](/flyctl) command. See [Showcase Architecture](https://github.com/rubys/showcase/blob/main/ARCHITECTURE.md) for an example of an application running in production that implements many of these ideas. ## Backups As mentioned above, backups are crucial. Items to explore: * [LiteFS](/litefs) - Distributed SQLite. By itself, it supports failover situations naturally. Additional [disaster recovery](/litefs/backup) options are available. * Sqlite3 databases are just files. Build a separate application to host backups and have your application periodically POST copies there. * [Rsync](https://rsync.samba.org/) is a utility available with Linux distributions that can be used to efficiently copy changes between Machines. * Run multiple Machines per cell so that you get the full benefits of traditional high availability configurations. If you have implemented an admin web UI, you can automate the deployment of new clusters easily. # Connect to your private network with WireGuard Source: https://docs.fly.io/blueprints/connect-private-network-wireguard/index Create a WireGuard peer with fly wireguard create and connect your laptop to your Fly.io organization's private network to reach apps and Flycast services. [YouTube video player](https://www.youtube-nocookie.com/embed/4NcvlIlIlso?si=DPbDPwzlRTQFx0hB) Every [Fly.io](http://Fly.io) organization comes with a [private network](/networking/private-networking) that lets all your apps connect to each other. This is super convenient when you need to have microservices call each other’s endpoints or use [Flycast](/networking/flycast) to let your private apps turn off when you’re not using them. However, this isn’t just limited to your apps. You can jack into this network with WireGuard. This blueprint shows you how to create a WireGuard peer to your private network and connect to it so that you can access it from anywhere. ## Prerequisites To get started, you need to have the following: * A [fly.io](http://fly.io) account * `flyctl` installed ([https://fly.io/docs/hands-on/install-flyctl/](https://fly.io/docs/hands-on/install-flyctl/)) * The [WireGuard client](https://www.wireguard.com/install/) installed ## Steps To create a WireGuard peer, you need the following information: * The organization you want to create the peer in, such as your personal organization. * The [Fly.io region](/reference/regions) that’s closest to you. * The name of the peer, such as your computer’s hostname. * A filename to save the configuration to. ``` $ fly wg create --help Add a WireGuard peer connection to an organization Usage: fly wireguard create [org] [region] [name] [file] [flags] ``` You can figure out your list of organizations with `fly orgs list`: ``` $ fly orgs list Name Slug Type ---- ---- ---- Xe Iaso personal PERSONAL devrel-demos devrel-demos SHARED ``` You can figure out which region is nearest you with `fly platform regions` or by checking the [regions page](/reference/regions) in the documentation: ``` $ fly platform regions ``` With all this in mind, let’s assemble the command. Each of these steps is going to build up the command, but don't hit enter until it's all done. Start with the base command `fly wireguard create`: ``` $ fly wireguard create ``` I want to create this in my personal organization, so I’ll enter in personal for the organization name. ``` $ fly wireguard create personal ``` I’m in Ottawa, so I’m using the Montreal region `yul`. ``` $ fly wireguard create personal yul ``` My computer’s hostname is Camellia, so I’ll use that as the peer name. ``` $ fly wireguard create personal yul camellia ``` Finally I want to save this as camellia.conf so that WireGuard can load it. ``` $ fly wireguard create personal yul camellia camellia.conf ``` Then run the command (you can hit enter now), and once it’s done open up the WireGuard app. Import the tunnel from the configuration file and then turn it on. macOS may prompt if you want the WireGuard app to manage VPN connections. If it does, hit accept, otherwise you won’t be able to get into your network. The above video shows how you import a WireGuard config on macOS. Here's a summary of the steps: * Click on the "Import tunnel(s) from file" button. * Select the configuration file you saved earlier. * Click on the "Activate" button. * macOS will ask you if you want to allow the WireGuard app to manage VPN connections. Click "Allow" if you trust it. On a Linux system you can use [wg-quick](https://www.man7.org/linux/man-pages/man8/wg-quick.8.html): ```sh theme={null} wg-quick up camellia.conf ``` To test the connection, ping `_api.internal` (on macOS you need to run `ping6 _api.internal` because it's an IPv6 address): ``` $ ping6 _api.internal -c4 PING6(56=40+8+8 bytes) fdaa:3:9018:a7b:9285:0:a:602 --> fdaa:3:9018::3 16 bytes from fdaa:3:9018::3, icmp_seq=0 hlim=64 time=9.741 ms 16 bytes from fdaa:3:9018::3, icmp_seq=1 hlim=64 time=49.103 ms 16 bytes from fdaa:3:9018::3, icmp_seq=2 hlim=64 time=97.667 ms 16 bytes from fdaa:3:9018::3, icmp_seq=3 hlim=64 time=14.726 ms --- _api.internal ping6 statistics --- 4 packets transmitted, 4 packets received, 0.0% packet loss round-trip min/avg/max/std-dev = 9.741/42.809/97.667/35.111 ms ``` To test the connection with an instance of [Ollama](https://ollama.com), you can fire it up with the following commands in an empty folder (don't forget to delete it after!): ``` fly launch --from https://github.com/fly-apps/ollama-demo --no-deploy fly ips allocate-v6 --private fly deploy ``` Then you can set the `OLLAMA_HOSTNAME` environment variable to the hostname of the instance you just created (it will be something like `xe-ollama.flycast`), and then run the following commands: ``` export OLLAMA_HOST=http://xe-ollama.flycast ollama run llama3 "Why is the sky blue? Explain in a single sentence." ``` And then the model will reply with something like this: > The sky appears blue because of a phenomenon called Rayleigh scattering, where shorter wavelengths of light (like blue and violet) are scattered more than longer wavelengths (like red and orange) by tiny molecules of gases like nitrogen and oxygen in the Earth's atmosphere. And there you go! You're in your private network and can access all your apps and Machines like they were right next to you. # Route requests to per-user Fly Machines Source: https://docs.fly.io/blueprints/connecting-to-user-machines Route HTTP requests to per-user Fly Machines through a router app with fly-replay, including port matching, authenticating replayed requests, and antipatterns. ![Illustration by Annie Ruygt of a bird holding hands with a computer](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/connecting-to-users.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=c2849c49c7a29afafc29afd1f5e509f0) ## 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 ![HTTP Request Flow](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/blueprints/connecting-to-user-machines-http-flow.svg?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=827fb73208b6104a3c215b52a105f242) 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](/networking/dynamic-request-routing#how-fly-replay-works). 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](/networking/dynamic-request-routing#how-fly-replay-works)). 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 theme={null} { "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 theme={null} // 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](/blueprints/per-user-dev-environments) Find out how to run isolated, on‑demand environments for each user. * [Observability for User Apps](/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](/networking/dynamic-request-routing) Learn about the `fly‑replay` header and dynamic request routing. # Custom deploy workflows: staggered and selective deploys Source: https://docs.fly.io/blueprints/custom-deploy-workflows Roll out fly deploy to stopped Machines, one region, or one process group at a time, to deploy gradually without restarting Machines running long jobs. ![Illustration by Annie Ruygt of a Frankie the hot air balloon writing on a notepad about different modes of transport including a jet a car and a truck](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/custom-deploy-workflows.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=2d44ce1b4d4569fc5539703695791a34) ## Overview > [!NOTE] > > **This guide shows how to take control of your deployment flow, whether you're trying to avoid restarting machines, roll out changes gradually, or update specific parts of your app without touching others.** Most people think of `fly deploy` as an all-or-nothing operation: you push a new image, and all your machines restart to run it. That behavior makes sense for many apps, but your app might need a more granular approach. Maybe you want to avoid interrupting machines processing long-running jobs (like video encoding or LLM chat sessions), or you want to gradually roll out changes to certain regions first. The `fly deploy` command supports several flags for targeting specific machines by ID, region, or process group, giving you the control to tailor your rollout strategy to your app's needs. ## Why you might want this These strategies are useful when you want to roll out code without disrupting active work. Maybe you're: * Handling long-running tasks that shouldn't be interrupted mid-process * Running workloads with unpredictable activity spikes where idle machines should be reused * Trying to deploy more cautiously in production by segmenting your rollout * Building your own logic to manage machine lifecycle instead of relying on a central deploy command They're especially useful for apps that: * Encode media, process jobs, or run background workers * Exit cleanly after finishing a task * Benefit from reusing stopped machines instead of spinning up new ones constantly ## Staggered and selective deploys You don’t have to update your whole fleet all at once. Fly gives you several ways to target machines for more careful rollouts. ### By status: stopped machines If your machines exit when they finish work, you can update only the stopped ones. This is useful for long-running tasks, background workers, or apps using `auto_stop_machines`. ```bash theme={null} # Build the new image and push it to the registry, but don't deploy it yet fly deploy --build-only --push # List all machines, filter for those in a "stopped" state, and save their IDs fly machines list --json \ | jq -r '.[] | select(.state == "stopped") | .id' \ > stopped-machines.txt # Deploy the new image only to the stopped machines fly deploy --image THE_IMAGE \ --only-machines $(cat stopped-machines.txt | paste -sd, -) ``` Repeat this process until all machines have rotated. This works best when: * You're using `auto_stop_machines` * Your app exits cleanly when done (`System.exit(0)` or equivalent) * You want machines to manage their own lifetimes ### By region To contain the blast radius or do a phased rollout, target machines by region: ```bash theme={null} # Deploy the current app image only to machines in the "dfw" region fly deploy --regions dfw ``` Or combine with `--only-machines` to target specific IDs within a region: ```bash theme={null} # List all machines, filter for those in the "dfw" region, and save their IDs fly machines list --json \ | jq -r '.[] | select(.region == "dfw") | .id' \ > dfw-machines.txt # Deploy to just the first two machines from that list head -n 2 dfw-machines.txt > batch-1.txt fly deploy --image THE_IMAGE \ --only-machines $(paste -sd, - < batch-1.txt) # Deploy to the next three machines that weren't in batch 1 head -n 5 dfw-machines.txt | grep -vxFf batch-1.txt > batch-2.txt fly deploy --image THE_IMAGE \ --only-machines $(paste -sd, - < batch-2.txt) ``` Extend this rollout gradually by increasing the number of machines targeted in subsequent deploys. If you want to go further, you can script this logic to apply region by region, or even automate it based on health checks or metrics from your app. ### By process group In multi-process apps, you might want to update only certain roles while leaving others untouched. For example, consider a cluster with `zk`, `supervisor`, and `conductor` process groups. If the `zk` machines need to stay up during an upgrade, you can deploy only to the other groups: ```bash theme={null} # Deploy the new image only to the "supervisor" and "conductor" process groups fly deploy --image THE_IMAGE --process-groups supervisor,conductor ``` This allows the `zk` group to keep running on the previous image until you're ready to update them. This pattern is helpful for maintaining quorum or minimizing downtime in clustered environments. ## Handling partial rollout failures Sometimes an image rolls out cleanly to some machines but fails on others. You should: * Watch for startup errors or unhealthy states * Retry failed updates individually * Pause rollouts if failure rates climb Eventually, you might want to implement an automated rollback based on health checks or deploy output. For now, just keeping track of which machines succeeded and which didn’t is a good start. ## CI integration If you're using GitHub Actions or another CI, you can bake this deployment pattern in. For instance: * `fly deploy --build-only --push` on main * Update stopped machines using the image tag as part of a scheduled job * Optionally notify Slack or GitHub comments when a full rollout completes This lets you deploy continuously without interrupting running tasks. ## Dynamic config updates Not every change needs a new image. For apps that load config from volumes or environment variables, you can: * Update secrets or config maps with `fly secrets set` * Restart idle machines to pick up the change * Let active machines shut down naturally and restart with updated config This is especially useful when your app supports live reloading or watches config files. ## Things to watch out for ### Forgetting to update all machines If your script misses some stopped machines, or if others stop later, they might keep running an old image. Make sure your update loop is thorough and runs long enough to catch all of them. ### Long-lived machines never get updated If your deployment strategy only updates stopped machines but your machines never exit on their own, they'll never upgrade. The most robust solution is to ensure you're using `auto_stop_machines` and that your app exits cleanly when it's done. If that’s not possible, you might configure your app to shut itself down after a fixed amount of time (a kind of time-to-live or TTL). Alternatively, use health checks to detect stale or idle machines, or forcibly stop them after a deadline using automation. ### Mixed-image behavior When machines are on different image versions, your app needs to handle it. Schema changes, protocol mismatches, or config divergence can cause issues if you don’t plan for this. ### Hidden dependencies on deploy Even if you're not restarting machines, some changes can still cause issues: environment variables, shared storage, or secrets might behave differently or get out of sync. ## Related reading Want to go deeper on the ideas in this guide? Check out these Fly.io docs: * [Machines overview](/machines): Learn how Machines work and what makes them different. * [fly deploy CLI reference](/flyctl/cmd/fly_deploy): Full list of deploy options including `--only-machines`, `--regions`, and `--process-groups`. * [Dynamic request routing](/networking/dynamic-request-routing): Use `fly-replay` to shift traffic between machines on the fly. # Deno KV with LiteFS Cloud Source: https://docs.fly.io/blueprints/deno-kv-litefs Back Deno KV with a SQLite database on LiteFS to run a Deno Deploy app on Fly.io. LiteFS Cloud has been sunset, so this guide is kept for reference. > [!WARNING] > > **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). > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. ## Problem If you're porting an app from Deno Deploy to Fly.io, you may hit a bit of a snag: if it was built with [Deno KV](https://deno.com/blog/kv), a *traditionally* FoundationDB backed K/V store made for Deno Deploy, there's no obvious way to easily run Deno KV at scale on Fly.io. You *could* use [denoland/denokv](https://github.com/denoland/denokv) on Fly.io, but due to an alignment of the stars there happens to be an easier way. Deno KV can actually be backed by an SQLite DB stored on disk in a cache folder, and you can specify the path of this DB with the parameter to `Deno.openKv( )`. This means you can do something like this to get Deno KV to use an arbitrary SQLite DB: ```typescript theme={null} const kv = await Deno.openKv("/any/path/i/want.db"); ``` Now, Fly.io has some special features for SQLite users, namely [LiteFS Cloud](/litefs/speedrun), a distributed file system that transparently replicates and backs up SQLite databases across all your instances. The magic here is that you can just treat it like a local on-disk SQLite database but behind the curtain it’s doing all the work to replicate your DB. ## Setting up LiteFS Firstly, you need to add a `litefs.yml` file to your project, and make sure it gets included in the Dockerfile. Here’s an example: ```yml theme={null} fuse: dir: "/litefs" data: dir: "/var/lib/litefs" exit-on-error: false proxy: addr: ":8080" target: "localhost:8081" db: "db" passthrough: - "*.ico" - "*.png" exec: - cmd: "deno run -A main.ts" lease: type: "consul" advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202" candidate: ${FLY_REGION == PRIMARY_REGION} promote: true consul: url: "${FLY_CONSUL_URL}" key: "litefs/${FLY_APP_NAME}"\ ``` This is 99% the [sample config](https://github.com/superfly/litefs-example/blob/main/fly-io-config/etc/litefs.yml), only changing the exec command to a Deno one. > [!NOTE] > > It's not immediately clear based on the example config, but your app needs to listen on "target" (:8081) and your service/http\_service in fly.toml needs to listen on "addr" (:8080). This is because part of LiteFS acts as a proxy, so you need the fly-proxy to send requests to LiteFS which then forwards them on to your app. > > If you’re not able to change your application’s port, make sure `target` is set up correctly then change `addr` and `fly.toml` to something other than `:8080`. Once you've added the config, you just need to make your app look for the SQLite DB in the right location. For Deno KV, that looks like this: ```typescript theme={null} const kv = await Deno.openKv(Deno.env.get("DB_LOCATION")); ``` Where `DB_LOCATION` is set to `/litefs/my.db` in `fly.toml` under `[env]`. Now, if you’re deploying to Fly.io, you’re almost ready to go. [Here’s where to look if you’re not running on Fly.io](/litefs/getting-started-docker). ## Creating a LiteFS Cloud cluster For a more in-depth article, check out [Getting Started with LiteFS on Fly.io](/litefs/getting-started-fly). Head over to the [LiteFS section](https://fly.io/dashboard/personal/litefs) of the dashboard and create a cluster, **make a note of the auth token** (you’ll need it later). ### Dockerfile You need to add the dependencies for LiteFS to your Dockerfile: ```docker theme={null} # for alpine-based images RUN apk add ca-certificates fuse3 sqlite # or for debian/ubuntu-based images RUN apt-get update -y && apt-get install -y ca-certificates fuse3 sqlite3 ``` And copy in the LiteFS binary: ```docker theme={null} COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs ``` And update the `ENTRYPOINT`/`CMD`: ```docker theme={null} ENTRYPOINT litefs mount ``` Now you're ready to launch your Fly app! ```bash theme={null} # Create a volume $ fly volumes create litefs --size 10 # Create your app without deploying $ fly launch --no-deploy # Attach the Fly.io provided Consul server to your app. $ fly consul attach # Set the secret for LiteFS cloud, replace the example value with your token from earlier $ fly secrets set LITEFS_CLOUD_TOKEN=yoursecrettoken ``` Update your `fly.toml` to mount the LiteFS volume: ``` [mounts] source = "litefs" destination = "/var/lib/litefs" ``` Aaannnndddd, Deploy! ``` fly deploy ``` Your reward for running all those commands should be a running app with LiteFS! Let us know if you run into friction using LiteFS with Deno KV or any other client, you can reach out on [the Fly Community](https://community.fly.io/). ### Scaling Deploying just one node using LiteFS doesn’t actually *do much*, so try scaling outwards! You can add a few Machines in regions close to your users like this: ``` fly scale count 3 -r yyz,ewr,waw ``` This will automatically add volumes and Machines to [scale your app](/launch/scale-count#scale-an-apps-regions) out and around the world! # Deploy OpenClaw on Fly.io Source: https://docs.fly.io/blueprints/deploy-openclaw Deploy the OpenClaw AI agent gateway on Fly.io with a download-and-run script that creates the app, volume and secrets, then connect Discord, Telegram or Slack. ![Illustration by Annie Ruygt of some little characters walking along](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/open-claw.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=14ca3741d9d01ca9f8d889d18d3b1600) To deploy [OpenClaw](https://github.com/openclaw/openclaw) to Fly.io, download the deploy package and run the script. It handles everything — app creation, volumes, secrets, and deployment. [Download Deploy Package](https://github.com/fly-apps/Openclaw-Fly-Deploy/archive/refs/heads/main.zip) ```bash theme={null} unzip Openclaw-Fly-Deploy-main.zip cd Openclaw-Fly-Deploy-main bash deploy.sh ``` You'll need **[flyctl](/flyctl/install)** installed, a **Fly.io account** ([free trial](/about/free-trial) works), and an **LLM API key** (Anthropic, OpenAI, Google Gemini, OpenRouter, Moonshot AI, or MiniMax). ## What is OpenClaw? OpenClaw is an open-source AI agent gateway that lets you run a persistent AI assistant reachable from anywhere — Discord, Telegram, Slack, or your local CLI. It manages conversations, tool use, and connections across multiple channels through a single gateway process. ## How it works The deploy package sets up a wrapper server that manages the OpenClaw gateway and provides a browser-based setup wizard: ``` Internet → Fly.io proxy → Wrapper server (:3000) → OpenClaw gateway (:18789) ├── /setup → Setup wizard (password-protected) ├── /healthz → Health check (no auth) └── /* → Proxied to gateway ``` All state lives on a persistent volume mounted at `/data`, so your configuration, conversation history, and installed tools survive restarts and redeployments. The script will prompt you for: 1. **App name** — defaults to `openclaw-XXXX` (random suffix). This becomes your URL: `https://your-app-name.fly.dev` 2. **Region** — where to run your Machine (defaults to `iad` / Virginia). Pick one close to you for lower latency. See [Fly.io regions](/reference/regions) for the full list. 3. **Setup password** — protects the `/setup` wizard from the internet. Pick something strong. 4. **LLM provider** — choose from Anthropic, OpenAI, Google Gemini, OpenRouter, Moonshot AI, or MiniMax 5. **LLM API key** — the key for your chosen provider 6. **Channel tokens** (optional) — Discord bot token, Telegram bot token, or Slack bot + app tokens if you want to connect chat channels The script then creates a Fly app, provisions a persistent volume, sets your credentials as encrypted [Fly secrets](/apps/secrets), and builds and deploys the Docker image on Fly's remote builders. The first deploy takes a few minutes. Your credentials never leave your machine — they go directly to Fly.io via `flyctl`. ## Post-deploy setup Once deployment completes, the script prints your app details: ``` === Deployment Complete === App URL: https://your-app-name.fly.dev Setup wizard: https://your-app-name.fly.dev/setup Gateway URL: wss://your-app-name.fly.dev Gateway token: ``` ### Setup wizard Visit `https://your-app-name.fly.dev/setup` in your browser. Log in with any username and the setup password you chose. From the wizard you can: * Change your LLM provider and API key * Add or update Discord, Telegram, and Slack channel connections * Edit the raw OpenClaw config file * Run debug commands against the gateway * Export and import configuration backups * Approve device pairing requests ### Connect your local CLI If you have OpenClaw installed locally, point it at your remote gateway: ```bash theme={null} openclaw config set gateway.mode remote openclaw config set gateway.remote.url wss://your-app-name.fly.dev openclaw config set gateway.remote.token openclaw health # verify the connection ``` This lets you use the `openclaw` CLI on your laptop while the gateway runs on Fly.io, keeping conversations and state persistent even when your laptop is closed. ## Configuration ### Secrets All sensitive values are stored as [Fly secrets](/apps/secrets), encrypted at rest and injected as environment variables at boot. They are never visible in logs, config files, or the Fly dashboard. | Secret | Required | Description | | - | - | - | | `SETUP_PASSWORD` | Yes | Protects the `/setup` wizard with HTTP Basic Auth | | `OPENCLAW_GATEWAY_TOKEN` | Yes | Auth token for gateway connections (auto-generated) | | `OPENCLAW_API_KEY` | Yes | Your LLM provider API key | | `OPENCLAW_AUTH_CHOICE` | Yes | Provider identifier (set by deploy script) | | `OPENCLAW_API_KEY_FLAG` | Yes | CLI flag for the provider (set by deploy script) | | `OPENCLAW_DISCORD_TOKEN` | No | Discord bot token | | `OPENCLAW_TELEGRAM_TOKEN` | No | Telegram bot token | | `OPENCLAW_SLACK_BOT_TOKEN` | No | Slack bot token (`xoxb-...`) | | `OPENCLAW_SLACK_APP_TOKEN` | No | Slack app token (`xapp-...`, required if bot token is set) | To update a secret after deployment: ```bash theme={null} fly secrets set OPENCLAW_API_KEY=sk-new-key-here -a your-app-name ``` The Machine restarts automatically when secrets change. ### VM sizing The default configuration uses a `shared-cpu-2x` Machine with 4 GB RAM, which costs roughly \$20–25/month when running continuously. With auto-stop enabled (the default), you only pay for time the Machine is actually running. To adjust resources: ```bash theme={null} fly scale memory 4096 -a your-app-name fly scale vm shared-cpu-4x -a your-app-name ``` ### Persistent storage OpenClaw stores all state on a [Fly Volume](/volumes/overview) mounted at `/data`. This includes: * `openclaw.json` — gateway configuration * Conversation history and context * Installed tools and plugins * npm/pnpm caches for user-installed packages The default volume size is 1 GB. To extend it: ```bash theme={null} fly volumes extend -s 3 -a your-app-name ``` ## Useful commands | Command | Description | | - | - | | `fly logs -a your-app-name` | Stream live logs | | `fly ssh console -a your-app-name` | SSH into the Machine | | `fly apps restart your-app-name` | Restart after config changes | | `fly scale memory 4096 -a your-app-name` | Increase memory | | `fly status -a your-app-name` | Check Machine status | | `fly volumes list -a your-app-name` | List attached volumes | ## Troubleshooting **"SETUP\_PASSWORD is not set"** The setup password secret is missing. Set it: ```bash theme={null} fly secrets set SETUP_PASSWORD=your-password -a your-app-name ``` **Out of memory / crashes** Increase the Machine's memory: ```bash theme={null} fly scale memory 4096 -a your-app-name ``` **Gateway won't start** Visit `/setup` in your browser and check the debug console for errors. Common causes: invalid API key, missing config file, or a corrupted state directory. **Lock file errors** If the gateway didn't shut down cleanly, stale lock files can prevent it from starting: ```bash theme={null} fly ssh console -a your-app-name rm -f /data/gateway.*.lock exit fly apps restart your-app-name ``` **Need to start fresh** Use the "Reset" button in the setup wizard to clear the config and re-run onboarding, or SSH in and remove the config: ```bash theme={null} fly ssh console -a your-app-name rm /data/openclaw.json exit fly apps restart your-app-name ``` ## Supported LLM providers | Provider | What you need | | - | - | | Anthropic | API key from [console.anthropic.com](https://console.anthropic.com/) | | OpenAI | API key from [platform.openai.com](https://platform.openai.com/) | | Google Gemini | API key from [aistudio.google.com](https://aistudio.google.com/) | | OpenRouter | API key from [openrouter.ai](https://openrouter.ai/) | | Moonshot AI | API key from Moonshot's developer portal | | MiniMax | API key from MiniMax's developer portal | You can switch providers at any time through the setup wizard — no redeployment needed. # Run HIPAA-compliant healthcare apps Source: https://docs.fly.io/blueprints/going-to-production-with-healthcare-apps Evaluate Fly.io for HIPAA: encryption in transit and at rest, network isolation, and access control. Then launch a pilot, sign a BAA, and deploy to production. ![Illustration by Annie Ruygt of a balloon doctor using stethescope on an app](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/production-healthcare.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=8d8bb266d53322393b0e1c476ece0326) ## 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 guide 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 The Health Insurance Portability and Accountability Act (HIPAA) sets the standard for protecting sensitive patient data. Any company that processes protected health information (PHI) must ensure that all the required physical, network, and process security measures are in place and followed. Fly.io takes a "principle of least privilege" approach to security. Here are the highlights of what healthcare app developers get out of the box on Fly.io: ### Data Protection and Encryption 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](/volumes), aligning with the encryption standards required by HIPAA for storage security. ### Access Control Access to PHI must be limited to authorized personnel only, which is managed through: * **Network Isolation**: Each application on Fly.io runs within its own private network by default, reducing the risk of unauthorized access. * **Secrets Management**: Fly.io provides a [secrets manager](/apps/secrets) for storing and handling API credentials and environment variables securely, ensuring that sensitive information is accessible only to authorized applications. * **Isolated Application Environments**: Fly.io allows organizations to create separate organizations for different environments, ensuring that production environments are isolated from test and staging environments. * **Workload Isolation:** [Docker workloads on Fly.io are isolated at the kernel hardware level through the Firecracker VM runtime](https://fly.io/blog/fly-machines/). These workloads are hosted in secure data centers on Fly.io's dedicated hardware, ensuring HIPAA compliance by minimizing the risk of unauthorized access and maintaining strict control over PHI data. ### Audit Controls HIPAA demands that you implement hardware, software, and procedural mechanisms that record and examine activity in systems containing PHI: * **Logging and Monitoring**: Fly.io allows organizations to monitor and log access and usage, helping comply with HIPAA’s information system activity review requirements. * **Version-Controlled Configuration**: Fly.io uses a Dockerfile and fly.toml configuration file for managing application deployments. These files can be checked into version control systems, like Github, allowing for comprehensive audit trails of infrastructure changes. This capability supports HIPAA's mandates for traceability and accountability in the modification and management of systems handling PHI. ### Data Integrity 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](/volumes/volume-manage) of [encrypted volumes](/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 Physical access to data centers where PHI is stored must be controlled: * **Data Centers**: Fly.io hosts services in secure data centers with restricted access, surveillance, and environmental controls, ensuring the physical security of the hardware that stores and processes PHI. * **Regional Data Control**: Fly.io allows users to control the [physical region](/reference/regions) where data is stored and processed using simple commands, facilitating compliance with data residency requirements of HIPAA by ensuring PHI is stored within designated secure regions, such as the United States. There's more detail at [https://fly.io/docs/about/healthcare](/about/healthcare) and [https://fly.io/security](https://fly.io/security). If you have additional questions about Fly.io's security infrastructure & practices, please [reach out](mailto:sales@fly.io). ## Launch pilot application The fastest way to deploy a pilot application is to run through the [Fly.io Speedrun](https://fly.io/speedrun/). If your free demo account doesn't have enough resources for your pilot application, you may [upgrade to a paid plan](/about/pricing) or [contact us](mailto:sales@fly.io) to make other arrangements. ### Specify deployment regions Fly automatically detects which of its 30+ regions around the globe is closest to you as your default data region. If the default region is not where you want to host your application, run `fly platform regions` to see the most recent list of regions. ```bash theme={null} fly platform regions NAME CODE GATEWAY 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... ``` Then select your desired region: ``` $ fly regions add ord ``` And remove undesired regions as well: ```bash theme={null} $ fly regions remove sin ``` Your application will automatically be deployed to the regions specified and communications between regions happen over a private, encrypted WireGuard network. ### Add API keys and credentials to the Secrets Manager Fly.io's [secret manager stores](/apps/secrets) your application's API credentials and secrets in a secure vault. The contents remain encrypted and are only available from running Machine instances as environment variables. ### Talk to our solution architects if you have questions If at any time during your evaluation of Fly you have questions about the infrastructure, security, or service terms you can ask in the community forums or [email Fly.io with your questions](mailto:sales@fly.io). ## Deploy to production Once you've evaluated Fly.io and have a BAA, it's time to go to production. The remainder of this guide will walk you through how to setup an isolated production environment, control access, and deploy your application. ### Sign a Business Associate Agreement (BAA) When you're ready to start deploying HIPAA apps, you'll need to do some paperwork (don't worry, we use digital signatures) to make sure everything is compliant. * [Choose the Compliance Package](https://fly.io/compliance) that includes HIPAA/BAA documents. * Sign in to the dashboard and request a signed BAA at [https://fly.io/dashboard/personal/compliance](https://fly.io/dashboard/personal/compliance) or [contact us](mailto:sales@fly.io) and we'll help. ### Provision an isolated production environment Ensure the production environment to be separate from the test and staging environments used to test this application. Fly uses "organizations" to control access to "applications". Since production environments require their own access control, we'll create a new organization that will contain all of our production applications. ```bash theme={null} # Replace $MYORG with the name of your organization $ fly orgs create $MYORG-production ``` You'll walk through the steps of creating a new organization within Fly.io. You'll also have to add a new payment method and select the appropriate plan for the new organization. If you need help during this process or have questions about how a plan spans multiple organizations, [please reach out](mailto:sales@fly.io). ### Invite team members to production environment Grant access to team members who may have access to the production environment: ```bash theme={null} $ fly orgs invite somebody@$MYORG.com --org $MYORG-production ``` ### Launch application Next, ensure you have a `fly.toml` file for each environment: ```bash theme={null} $ cp fly.toml fly.staging.toml $ mv fly.toml fly.production.toml ``` Run `fly launch` to provision the production environment. ```bash theme={null} # Replace $MYAPPNAME with the name of your app $ fly launch --name $MYAPPNAME-production --path fly.production.yml --org $MYORG-production An existing fly.toml file was found for app $MYAPPNAME ? Would you like to copy its configuration to the new app? (y/N) y ``` You'll be asked to copy the configuration file. Select `y` assuming you want to use the same settings from the `fly.toml` file that was formerly staging. You'll want to commit these `fly.toml` files to your repo. If you run into problems during or after deploy you can run `fly logs --path fly.production.yml` to see errors your application may be logging. ### Create SSL certificate and custom domain Next, point a domain name to the new production environment via `fly certs`. ```bash theme={null} $ fly certs add myappname.com --path fly.production.yml ``` The output of the command includes the information you need to update DNS records to point to the application on Fly.io ### Deploy application Finally run `fly deploy --path fly.production.yml` to deploy your application, then open your application in your browser to ensure that it's up and running. ## 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](/about/healthcare) Overview of how Fly.io supports HIPAA‑compliant apps, including encryption, network isolation, and audit‑controls. * [Going to production checklist](/apps/going-to-production) A general production‑readiness checklist for apps on Fly.io. # Run Hermes Agent on Fly.io Source: https://docs.fly.io/blueprints/hermes-agent-on-fly-io Run the Hermes AI agent from Nous Research on a Fly.io Machine with a volume for its state, configure an LLM key, and reach its web dashboard through fly proxy. ![Illustration by Annie Ruygt of the Greek god Hermes mid-flight, depicted on a black-figure pottery bowl framed by olive branches](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Hermes_Agent.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=197b090d781e244b284d168be06ac878) [Hermes](https://github.com/NousResearch/hermes-agent) is an AI agent from Nous Research with a built-in learning loop: it watches its own output, notices when it had to improvise to finish a task, and writes that experience back as a reusable skill it can call next time. The skill library grows with use, so Hermes works best when it runs continuously on a persistent host rather than a short-lived sandbox, which is exactly what a Fly Machine with an attached volume gives you. This guide walks you through running Hermes on a Fly Machine, configuring it, and reaching its web dashboard from your laptop. You'll need **[flyctl](/flyctl/install)** installed, a **Fly.io account** ([free trial](/about/free-trial) works), and an **LLM API key** (Anthropic, OpenAI, Google Gemini, or [OpenRouter](https://openrouter.ai/) for access to 200+ models). You'll use Nous Research's **official prebuilt image** (`nousresearch/hermes-agent:latest`) so there's no Dockerfile to maintain and no remote builder to wait on. Fly pulls the image straight from Docker Hub. ## Create the app and volume Hermes keeps all its state in `/opt/data` inside the container. That includes config, API keys, sessions, skills, and memories. You'll back that with a Fly volume so it persists across deploys and restarts. Pick an app name (must be globally unique on Fly) and a [region](/reference/regions) close to you, then: ```bash theme={null} fly apps create fly volumes create data -a --region --size 3 ``` 3 GB is comfortable headroom for sessions and the bundled skills directory. ## Write fly.toml Create a directory for the deployment config and drop a `fly.toml` in it: ```toml theme={null} app = "" primary_region = "" machine_config = "machine_config.json" [build] image = "nousresearch/hermes-agent:latest" [[mounts]] source = "data" destination = "/opt/data" [[vm]] memory = "4gb" cpus = 2 ``` The `machine_config` key points to a JSON Machine config file that defines the containers for a [multi-container Machine](/machines/guides-examples/multi-container-machines). Each entry in its `containers` array follows the [`ContainerConfig`](/api/machines/models#fly-containerconfig) schema from the Machines API. Create that file next to `fly.toml` (called `machine_config.json` in our example): ```json theme={null} { "containers": [ { "name": "hermes", "image": "nousresearch/hermes-agent:latest", "cmd": ["gateway", "run"] } ] } ``` A few notes: * **No `[build.dockerfile]`.** Fly pulls the image directly. Deploys take seconds, not minutes. * **Multi-container Machine for `s6-overlay`.** Hermes images published from `v2026.5.28` onward use `s6-overlay` as the in-container supervisor. s6-overlay's `/init` calls `s6-overlay-suexec`, which checks `getpid() == 1` before doing anything else and aborts otherwise. Running Hermes as a container in a multi-container Machine (via `machine_config.json`) gives it its own PID namespace, so `/init` runs as PID 1. * **No `[[services]]` block.** The gateway talks *outbound* to chat platforms, so you don't need a public port. The dashboard exposes API keys and shouldn't be public; you'll reach it through a Fly proxy tunnel below. * **4 GB / 2 CPU** is the recommended size when browser tools (Playwright/Chromium) are active. If you don't use browser tools you can drop to `shared-cpu-1x` and 1–2 GB. ### Optional: pass API keys as Fly secrets This guide enters API keys through `hermes setup` after the first deploy. If you'd rather manage keys with [Fly secrets](/apps/secrets), set the secrets before you deploy: ```bash theme={null} fly secrets set ANTHROPIC_API_KEY=sk-... -a ``` Then list each one under the container's `secrets` array in `machine_config.json`. Each entry is `{ "env_var": "" }`, where `` must exactly match a secret that already exists on the app: ```json theme={null} { "containers": [ { "name": "hermes", "image": "nousresearch/hermes-agent:latest", "cmd": ["gateway", "run"], "secrets": [ { "env_var": "ANTHROPIC_API_KEY" } ] } ] } ``` If a listed secret doesn't exist, the Machine won't start. The deploy times out without any error in `fly logs`. Run `fly secrets list -a ` to check. See [`MachineSecret`](/api/machines/models#fly-machinesecret) for the full entry format. ## Deploy ```bash theme={null} fly deploy -a --ha=false ``` `--ha=false` keeps it to a single Machine. By default, `fly deploy` creates two Machines for availability. Since Fly volumes can’t be shared between Machines, the second Machine would get an empty volume and run a separate Hermes instance with its own config, sessions, and skills. When the deploy finishes, the machine boots, the entrypoint bootstraps `/opt/data` (creating `.env`, `config.yaml`, `SOUL.md`, `sessions/`, `skills/`, etc.), and `hermes gateway run` starts. It'll keep running but it has no API key yet, so it can't talk to a model. Confirm it's alive: ```bash theme={null} fly logs -a ``` You should see the bundled skills sync, then the gateway starting up. ## Configure Hermes SSH into the machine. The `hermes` binary lives at `/opt/hermes/.venv/bin/hermes` inside the image, but `fly ssh console` opens a login shell that resets PATH and won't find it there. Add a symlink into `/usr/local/bin` (which is always on PATH) so `hermes` works as a bare command: ```bash theme={null} fly ssh console -a -C \ "ln -sf /opt/hermes/.venv/bin/hermes /usr/local/bin/hermes" ``` Then open a shell on the machine and run the setup wizard: ```bash theme={null} fly ssh console -a hermes setup ``` The wizard walks you through model selection, tool configuration, and connecting your messaging platforms. When it's done, exit the SSH session. Restart the machine so the gateway picks up the new config: ```bash theme={null} fly machine restart -a ``` Get `` from `fly machine list -a `. ## Web dashboard Hermes has a web dashboard on port 9119 for managing sessions, skills, and config. The dashboard reads your API keys, so the upstream guidance is to never expose it on a public port. Tunnel to it instead. The Fly proxy reaches the Machine over its private IPv6 address, which requires the dashboard to listen on all interfaces, not only localhost. When Hermes binds to anything other than a loopback address, it [requires dashboard auth](https://hermes-agent.nousresearch.com/docs/user-guide/features/web-dashboard#when-the-gate-engages). Set up a username and password first. Generate a session secret on your laptop: ```bash theme={null} openssl rand -base64 32 ``` Then open a shell on the machine and add the credentials to Hermes's `.env` on the volume, pasting the generated value as the secret: ```bash theme={null} fly ssh console -a cat >> /opt/data/.env <<'ENV' HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin HERMES_DASHBOARD_BASIC_AUTH_PASSWORD= HERMES_DASHBOARD_BASIC_AUTH_SECRET= ENV ``` Because they're on the volume, the credentials survive deploys and restarts. In the same shell (or a new `fly ssh console --pty` session), start the dashboard, bound to the IPv6 wildcard address. Pass `::` without square brackets: ```bash theme={null} hermes dashboard --no-open --host :: ``` In a second terminal, open a Fly proxy from your laptop: ```bash theme={null} fly proxy 9119:9119 -a ``` Now visit `http://localhost:9119` in your browser and sign in with the username and password you set. Traffic goes over your authenticated WireGuard tunnel; the dashboard isn't published to the public internet, though it is reachable from other Machines on your organization's [private network](/networking/private-networking) When you're done, `Ctrl+C` both commands. The gateway keeps running on the machine. ## Upgrading The image is stateless; your data lives on the volume. To pull the latest Hermes: ```bash theme={null} fly deploy -a fly ssh console -a -C \ "ln -sf /opt/hermes/.venv/bin/hermes /usr/local/bin/hermes" ``` The deploy pulls `nousresearch/hermes-agent:latest` again. The second command re-creates the `/usr/local/bin/hermes` symlink; it lives on the container's filesystem, not the data volume, so each new container starts without it. ## VM sizing If you're running heavy tool use or multiple concurrent sessions, scale up: ```bash theme={null} fly scale memory 8192 -a fly scale vm shared-cpu-4x -a ``` ## Useful commands | Command | Description | | - | - | | `fly logs -a ` | Stream live logs | | `fly ssh console -a ` | SSH into the machine | | `fly ssh console -a -C "hermes doctor"` | Health check | | `fly machine restart -a ` | Restart after config changes | | `fly status -a ` | Check machine status | | `fly volumes list -a ` | List attached volumes | | `fly ssh console -a -C "hermes skills list"` | List learned skills | ## Troubleshooting **Gateway won't start** Check `hermes doctor` for missing API keys or other diagnostics: ```bash theme={null} fly ssh console -a -C "hermes doctor" ``` **Deploy times out with "timeout reached waiting for machine's state to change"** If `fly logs` stops after "Successfully prepared image" and the Machine never starts, check that every secret in `machine_config.json`'s `secrets` array exists: ```bash theme={null} fly secrets list -a ``` A missing secret stops the Machine before Hermes runs, so Hermes itself logs nothing. **Out of memory** Increase RAM: ```bash theme={null} fly scale memory 8192 -a ``` **Need to start fresh** Wipe the config files (skills, sessions, and memories survive): ```bash theme={null} fly ssh console -a -C \ "sh -c 'rm -f /opt/data/config.yaml /opt/data/.env'" fly machine restart -a fly ssh console -a hermes setup ``` To wipe everything including conversations, destroy and recreate the volume: ```bash theme={null} fly machine stop -a fly volumes destroy -a fly volumes create data -a --region --size 3 fly machine start -a ``` **Skills behaving unexpectedly** List, view, and delete: ```bash theme={null} fly ssh console -a hermes skills list hermes skills view hermes skills delete ``` # Guides and Blueprints Source: https://docs.fly.io/blueprints/index Architecture patterns and how-to guides for Fly.io: per-user Machines, AI agents, MCP servers, deployments, private networking, scaling, and background jobs. 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. * [Deploy OpenClaw on Fly.io](/blueprints/deploy-openclaw) * [Run Hermes Agent on Fly.io](/blueprints/hermes-agent-on-fly-io) NEW!! * [Warm pools of user Machines](/blueprints/warm-pool-user-machines) NEW!! * [Deploying Remote MCP Servers](/blueprints/remote-mcp-servers) * [High availability: run multiple Machines](/blueprints/resilient-apps-multiple-machines) * [N-tier architecture](/blueprints/n-tier-architecture) * [Shared Nothing Architecture](/blueprints/shared-nothing) * [Sticky sessions (session affinity)](/blueprints/sticky-sessions) * [Multi-region databases with read replicas and fly-replay](/blueprints/multi-region-fly-replay) ## Deployment & Developer Workflow Stuff you set up once and adjust when you ship code. Includes previews, base images, staging, and Docker wrangling. * [Working with Docker on Fly.io](/blueprints/working-with-docker) * [Custom deploy workflows: staggered and selective deploys](/blueprints/custom-deploy-workflows) * [Zero-downtime deployments](/blueprints/seamless-deployments) * [Roll back a deploy](/blueprints/rollback-guide) * [Git Branch Preview Environments on Github](/blueprints/review-apps-guide) * [Separate staging and production environments](/blueprints/staging-prod-isolation) * [Per-User Dev Environments with Fly Machines](/blueprints/per-user-dev-environments) * [Use Docker base images for faster deploys](/blueprints/using-base-images-for-faster-deployments) * [Managing Docker Images with Fly.io's Private Registry](/blueprints/using-the-fly-docker-registry) ## Networking & Connectivity Connecting things. Public apps, private services, bridging deployments, SSH, and WireGuard. * [Run private apps with Flycast](/blueprints/private-applications-flycast) * [Connect to your private network with WireGuard](/blueprints/connect-private-network-wireguard) * [Connect AWS and other clouds with WireGuard](/blueprints/bridge-deployments-wireguard) * [Route requests to per-user Fly Machines](/blueprints/connecting-to-user-machines) * [Run an SSH server on a Fly Machine](/blueprints/opensshd) ## Scaling, Performance & Observability Make it fast. Make it reliable. Monitor what's happening. * [Stream logs from user apps with NATS](/blueprints/observability-for-user-apps) * [Autoscale Fly Machines with autostop or fly-autoscaler](/blueprints/autoscale-machines) * [Autostart and autostop private apps with Flycast](/blueprints/autostart-internal-apps) * [Setting Hard and Soft Concurrency Limits](/blueprints/setting-concurrency-limits) * [Using Fly Volume forks for faster startup times](/blueprints/volume-forking) * [Run HIPAA-compliant healthcare apps](/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. * [Long-running tasks and machine lifecycle](/blueprints/long-running-tasks) NEW!! * [Building Infrastructure Automation without Terraform](/blueprints/infra-automation-without-terraform) * [Background job queues with Celery, BullMQ, and Fly Machines](/blueprints/work-queues) * [Task scheduling guide with Cron Manager and friends](/blueprints/task-scheduling) * [Crontab with Supercronic](/blueprints/supercronic) # Building Infrastructure Automation without Terraform Source: https://docs.fly.io/blueprints/infra-automation-without-terraform Fly.io has no Terraform provider. Get repeatable deploys with flyctl and GitHub Actions, or call the Machines API directly for custom orchestration. ![Illustration by Annie Ruygt of a Frankie the balloon working on automation at his computer](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/building-infrastructure-automation.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8b0979ffd9b58dedc2686b79a54c465e) ## 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 2. Push the code to a GitHub repo 3. 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. > [!NOTE] > > **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](/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/${FLY_APP_NAME}/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](/launch/continuous-deployment-with-github-actions) Walk‑through on wiring CI/CD with Fly.io + GitHub Actions. * [Access Tokens for Fly.io](/security/tokens) Deep dive into token scopes, permissions, and best practices for automation workflows. * [Working with the Machines API](/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`)](/reference/configuration) Details on how your app config file works, which is often part of your automation setup. # Long-running tasks and machine lifecycle Source: https://docs.fly.io/blueprints/long-running-tasks Why auto_stop_machines can stop a Machine that's still running background work, how Fly Proxy decides a Machine is idle, and two patterns that keep tasks alive. This page covers what happens when your machine is busy doing work, but Fly thinks it's idle. Specifically: how `auto_stop_machines` decides what to stop, why a background task is invisible to that decision, and the two patterns that keep work from getting killed. If you're picking a queue technology or a cron runner, start with the [work queues](/blueprints/work-queues) or [task scheduling](/blueprints/task-scheduling) guides instead. This page is about the machine behavior underneath them. ## The problem A typical setup: a FastAPI endpoint accepts a request, spawns an async task to generate a report, returns `202 Accepted`, and closes the connection. The proxy sees no active connections. A few minutes later, it stops the machine. The report dies half-finished. This isn't a bug. It's `auto_stop_machines` working exactly as documented. The proxy looks at inbound traffic. It does not look inside the container. From the proxy's point of view, a machine running a 20-minute job and a machine doing nothing look identical. There are two ways to fix it. Pick one based on whether your work is bursty or steady, request-triggered or queue-driven. ## How autostop actually decides The Fly proxy evaluates machines every few minutes. The exact rule depends on how many machines you have: **Multiple machines.** The proxy uses your `soft_limit` [concurrency setting](/blueprints/setting-concurrency-limits) to compute excess capacity: ``` excess = num_machines − (num_machines_over_soft_limit + 1) ``` If `excess ≥ 1`, the proxy stops one machine. The `+ 1` keeps a buffer of one idle machine for incoming traffic. **Single machine.** Simpler: if load is zero, the proxy stops the machine. In both cases, "load" means traffic the proxy can see. Background work running inside the machine, whether that's async workers, cron-style loops, or anything else not driven by an inbound request, doesn't count. There's also no way for your application to tell the proxy, "I'm busy, leave me alone." This is the central fact for the rest of the guide. Everything below is a way to work around it. ### Stop vs. suspend `auto_stop_machines` takes three values: `"off"`, `"stop"`, and `"suspend"`. * **stop** shuts the machine down cold. A restart takes seconds (about 2s for a Rails app, less for a small binary). * **suspend** dumps the entire VM state (memory, CPU, network) to disk. Resume takes a few hundred milliseconds. **Stop** is the simpler default: the machine shuts down when it's idle and cold-starts when it's needed again. For most apps, that's the right tradeoff. **Suspend** is the right choice when cold start is too painful (slow framework boot, heavy initialization, large in-memory state) and you'd still like to idle when inactive. The tradeoff: suspend is rougher on the underlying platform and has more constraints: * Machines must have 4 GB of RAM or less. * Swap and schedules are not supported. * Machines updated before June 20, 2024 cannot be suspended. * Suspend is not durable. Fly does not guarantee that a suspended machine will resume. Host migration, maintenance, or capacity pressure can turn what would have been a resume into a cold start. Treat suspend as a faster version of stop, not a guaranteed warm restart. * A few log lines may be lost across a suspend/resume cycle, and the system clock can take a second or two to re-synchronize after resume. See "[Suspend vs. Stop](/getting-started/troubleshooting#suspend-vs-stop)" for details on clock skew. Billing is the same for both: you pay for stopped machines like you pay for suspended ones. For the rest of this guide, "stop" and "suspend" are interchangeable. The patterns work the same way for both. ## Pattern A: disable autostop, manage shutdown in the app **Use this when** your app has long-lived workers, in-process job runners, or any background work that the application itself can track. Turn autostop off in `fly.toml`: ```toml theme={null} [http_service] internal_port = 8080 auto_stop_machines = "off" auto_start_machines = true ``` With autostop off, the proxy never stops your machines for being idle. They stay up until something else stops them (a deploy, `fly machine stop`, or a host migration). You're paying for every machine 24/7, in every region you've scaled into, so make sure that's the right tradeoff before adopting this pattern. When deploys, manual stops, or host migrations *do* stop the machine, your app gets `SIGTERM` and has `kill_timeout` seconds to clean up. The default of 5 seconds is almost certainly too short. Bump it. These are top-level keys in `fly.toml`: ```toml theme={null} kill_signal = "SIGTERM" kill_timeout = "30s" ``` The maximum is 300 seconds. `kill_timeout` is a drain window, not a "let the job finish" knob. If your jobs run longer than 5 minutes, either checkpoint them so they can resume, or stop accepting new work and let in-flight jobs drain before the timeout. Don't wait for everything to finish. A minimal shutdown pattern in Node: ```javascript theme={null} let activeJobs = 0 let shuttingDown = false async function runJob(payload) { if (shuttingDown) throw new Error("shutting down") activeJobs++ try { await doWork(payload) } finally { activeJobs-- } } process.on("SIGTERM", () => { shuttingDown = true const start = Date.now() const deadline = 25_000 // 5s under kill_timeout const tick = setInterval(() => { if (activeJobs === 0 || Date.now() - start > deadline) { clearInterval(tick) process.exit(0) } }, 200) }) ``` In Python with asyncio: ```python theme={null} import asyncio, signal active = 0 shutting_down = asyncio.Event() async def run_job(payload): global active if shutting_down.is_set(): raise RuntimeError("shutting down") active += 1 try: await do_work(payload) finally: active -= 1 async def shutdown(): shutting_down.set() try: await asyncio.wait_for(_drain(), timeout=25) except asyncio.TimeoutError: pass async def _drain(): while active > 0: await asyncio.sleep(0.2) loop = asyncio.get_event_loop() loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(shutdown())) ``` Both patterns refuse new work as soon as `SIGTERM` arrives, then wait for in-flight jobs up to a deadline a few seconds shorter than `kill_timeout`. The safety margin matters, if you wait the full 30s, Fly's `SIGKILL` arrives before your `exit(0)` runs. ## Pattern B: split web and worker into separate process groups **Use this when** web traffic is bursty (a good candidate for autostop) but background work is steady or long-running (a bad candidate for autostop). Split with `processes` in `fly.toml`: ```toml theme={null} [processes] web = "bundle exec puma" worker = "bundle exec sidekiq" [http_service] internal_port = 8080 auto_stop_machines = "suspend" auto_start_machines = true processes = ["web"] ``` The worker process group has no `[http_service]` attached, so the proxy never touches its machines. Autostop applies only to the web tier. Scale them independently: ```bash theme={null} fly scale count web=2 worker=1 ``` This is the pattern Sidekiq, Celery, and BullMQ workers actually want. The web tier scales to zero off-hours; the worker tier runs whenever there's work in the queue. Tradeoff: you're paying for at least one worker machine continuously. If your work is batchy enough that on-demand workers make sense, use the [work queues guide's on-demand worker pattern](/blueprints/work-queues) instead, as that spins up a fresh machine per job and lets it stop when done. ## Graceful shutdown: what Fly sends When something stops your machine, whether that's `auto_stop_machines`, `fly machine stop`, a deploy, or a host migration, Fly sends `kill_signal` (default: `SIGTERM`) to PID 1. After waiting `kill_timeout` seconds, it sends `SIGKILL`. The defaults are conservative: | Option | Default | Max | Notes | | - | - | - | - | | `kill_signal` | `SIGTERM` | n/a | Also accepts `SIGQUIT`, `SIGUSR1`, `SIGUSR2`, `SIGKILL`, `SIGSTOP` | | `kill_timeout` | `5s` | `300s` | The drain window before `SIGKILL` | Five seconds is enough for an HTTP server to close keepalives. It is not enough for a long-running job to finish. If you have any background work, set `kill_timeout` to a value that allows your typical job to complete. You'll need to determine this on your side. Both keys are top-level in `fly.toml`: ```toml theme={null} kill_signal = "SIGTERM" kill_timeout = "30s" ``` PID 1 receives the signal. In a Docker container running your app directly, that's your process. In a container running a shell wrapper (`CMD ["sh", "-c", "..."]`), the shell is PID 1 and `SIGTERM` doesn't propagate. Use the exec form: `CMD ["myapp"]`, or `exec myapp` inside the wrapper. `kill_timeout` is not a "finish your work" timer. It's a drain window. Inside it, you should: 1. Stop accepting new work 2. Let in-flight work finish, or checkpoint it 3. Exit cleanly If your jobs take longer than 5 minutes, you can't drain them inside `kill_timeout`. You need either Pattern A with checkpoint/resume, or Pattern B with a worker tier that's never autostopped. Run `fly config validate --strict` before relying on any of this. By default, `fly config validate` silently accepts unrecognized sections and keys. A typo or outdated section name can pass validation and then do nothing at runtime. Strict mode catches those errors. ## Picking a pattern | Situation | Pattern | | - | - | | Jobs are short (\< 30 seconds) | Increase `kill_timeout`; everything else can stay as default | | Long-running jobs, steady web traffic | A: disable autostop, in-app drain | | Long-running jobs, bursty web traffic | B: split web/worker processes | | Cron-style scheduled jobs | See [task scheduling](/blueprints/task-scheduling) | | Queue-driven workers | B: combine with [work queues](/blueprints/work-queues) | | One-off jobs (fire and forget per request) | On-demand workers; see [work queues](/blueprints/work-queues) | | Can't restructure right now | A: accept the continuous machine cost | ## Common problems **My `SIGTERM` handler runs but the job still gets killed.** `kill_timeout` is shorter than your handler needs. Bump it (max 300s) and set your handler's deadline a few seconds under that. **The machine stops mid-job even with `auto_stop_machines = "off"`.** Autostop is only one of several things that stop machines. Deploys, `fly machine stop`, scale-down, and host migrations all do too. Check `fly logs` for the `instance refused` or `host migration` events. Pattern A still applies. The only difference is that autostop is no longer the trigger. **Why doesn't a self-ping keep my machine alive?** It won't. The [autostop reference](/reference/fly-proxy-autostop-autostart) defines idle as "a load of 0" but doesn't specify what counts as load. Empirically, sending a successful HTTP request every 60 seconds from a machine to its own `.fly.dev` hostname does not prevent autostop. The proxy still stops the machine after 5 to 10 minutes. To keep a machine running through idle traffic, turn off autostop (Pattern A) or move the work into a process group without `[http_service]` (Pattern B). **Worker machines won't stop when I deploy.** A process group with no `[http_service]`, such as the worker tier in Pattern B, is invisible to the proxy. Deploys still update those machines because `flyctl` talks to them directly, but the proxy does not manage their lifecycle and cannot autostop them. To stop them gracefully, send a signal with `fly machine stop` or let `fly deploy` replace them during a deployment. **Suspend resumes are slower than the docs say.** Suspend isn't durable. If Fly can't restore the snapshot (host migration, capacity pressure), you get a cold start. There's no flag to tell you which happened; check the first-request latency. If cold starts matter, run with `min_machines_running = 1`. ## Where to go next * [Work queues guide](/blueprints/work-queues): Picking a queue technology * [Task scheduling guide](/blueprints/task-scheduling): cron-style triggers and scheduled machines * [Autostart and autostop reference](/reference/fly-proxy-autostop-autostart): The proxy's full decision logic * [Configuration reference](/reference/configuration): `kill_signal`, `kill_timeout`, `processes`, `auto_stop_machines` * [Machine states](/machines/machine-states): what `stopping`, `stopped`, and `suspended` actually mean # Multi-region databases with read replicas and fly-replay Source: https://docs.fly.io/blueprints/multi-region-fly-replay Run your app with a primary database and regional read replicas on Fly.io, and use the fly-replay header to send write requests to the primary region. ![Illustration by Annie Ruygt of Frankie looking out over the solar system](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/multi-region.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=5c10e2813e342052ae0ea0870476d4ef) ## Overview You want to run your app and database in multiple [regions](/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 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. * You don't want the architecture complications and potential for conflicts that come with running a multi-primary database cluster. ## Fly.io was made for multi-region apps Fly.io already routes requests to the closest region where your app is deployed. Using regional database read replicas can further reduce latency since the Fly Machine (our fast-launching VM) receiving the request can connect to a database instance in the same region. Read replicas also reduce the overall load on the primary database. This multi-reader single-writer setup is great for read-heavy apps. When your app receives write requests, you can use the `fly-replay` response header to forward the whole HTTP request to the primary region, which is often faster than connecting directly to the primary database from other regions. This is especially true when a request to an endpoint requires multiple database queries to complete; there would be extra latency on each query from a secondary region compared to a local connection in the primary region. ## How it works The [`fly-replay` response header](/networking/dynamic-request-routing) instructs Fly proxy to redeliver (replay) the original request to another region or Machine in your app, or even another app in your organization. In this case, you’ll be replaying write requests to the Machine in the primary region. Using `fly-replay` to replay write requests is a general pattern that can be applied in most languages and frameworks for databases with one primary and multiple read replicas. In the following diagram, the app is running one Machine in each of three regions. The primary region is Chicago, and this is where the read/write primary database resides. There are Machines in two other regions, Rio de Janeiro and Amsterdam, each of which has a read replica. This example uses three regions for simplicity, but you could deploy in more than three regions and have more than one Machine per region connecting to the same read replica. ![Three Machines with attached databases in 3 regions: Chicago is the primary region with a writable database, while Rio de Janeiro and Amsterdam have read only replica databases. Arrows pointing from the secondary Machines to the primary Machine show the direction of HTTP requests redelivered using the fly-replay response header. Arrows pointing from the primary database to the read replicas indicate syncing of data.](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-fly-replay.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=96e1c18b3929b78a81f07fd6f31b430c) > [!NOTE] > > **Note:** To illustrate the `fly-replay` concept in our diagram, we show the replayed HTTP request going directly from Machines in Rio de Janeiro and Amsterdam to the Machine in Chicago. In real life, Fly Proxy routes the request back through an edge node first. The cost of this routing is small, but if extreme efficiency is important for your use case, you can run your app in more regions to mitigate that. Your app is running on Fly.io. The database can also run on Fly.io—in which case the app and database regions will match—or on another provider where you can pick the region closest to the Fly.io region of your Machines. ## How to make it work for your app Your app needs logic to: 1. **Read from the corresponding regional replica (or primary!) when a Machine receives a read request.** 2. **Always replay write requests to the primary region rather than connecting to the primary database from another region.** ### Read from the corresponding regional replica Your app has a configurable primary region and you'll also host your primary database there or, in the case of a separate database provider, as close as possible to it. The primary region is exposed in the `PRIMARY_REGION` environment variable on every Machine in your app. Each Machine exposes the region in which it's running in the `FLY_REGION` environment variable. Your app can check the `FLY_REGION` against the `PRIMARY_REGION`, and modify the `DATABASE_URL`, `DB_HOST`, or other database variables when the region codes don't match. In some cases this just means changing a port ([as for Postgres](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication#connecting-to-read-replicas)) and in other cases it will be more complex if the database requires different variables for each read replica (like in this [Laravel example](https://fly.io/laravel-bytes/multi-region-laravel-with-planetscale/)). ### Replay write requests to the primary region Your app can detect write requests and send a response with the `fly-replay` header that tells Fly Proxy to replay the whole request to the Fly Machine in the primary region. #### Detect write requests First, you'll need a way to detect write requests so that you can forward them to the primary database. How you detect write requests will depend on your app and database. With Postgres databases, probably the simplest way to detect write requests is by [catching “read only transaction” errors](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication#detecting-write-requests) when you attempt to write to a read replica. You can also set up routes in middleware if your language provides support for splitting read and write connections. Or your app can handle GET, POST, DELETE, PUT, and PATCH requests differently, again likely in your app's middleware. For example, assuming HTTP requests using GET are read requests and everything else is a write request will work well most of the time. #### Send a response with the fly-replay header When your app detects a write request, it should send a response that contains only the `fly-replay` header with the [region code](/reference/regions) of your primary region like so: `fly-replay: region=`. Fly Proxy will replay the whole request to the specified region. The code would look something like this: ```yaml theme={null} # This code should be in the handler for non-GET requests to the endpoint you # want to send to the primary region for write to the primary database. if os.environ("PRIMARY_REGION") != os.environ("FLY_REGION"): # This machine is not in the primary region response.headers = {"fly-replay": f"region={os.environ("PRIMARY_REGION")}"} return response else: # This machine *is* in the primary region # Handle request normally, write to database. ``` ## Challenges and limitations ### Write-heavy apps shouldn't use read replicas Don't use regional read replicas if your app's requests are more than 50% write requests. In that case, you'd want to look into a multi-region primary or similar solution. ### Reading your own writes There will be short periods of time when data is not consistent between replicas and the primary. If reading your own writes immediately is a requirement for your app, then you'll need to consider how to handle replication. You can set up health checks to monitor replication status and fail when replication is not complete. Another useful pattern is to poll the database until the data is replicated. The Fly Postgres [LSN module](https://github.com/superfly/fly_postgres_elixir/tree/main/lib/lsn) has an example of how to poll with a [Postgres stored procedure](https://github.com/superfly/fly_postgres_elixir/blob/main/lib/migrations/v01.ex). You can reuse this pattern from any database library. A less efficient method would be for your app to catch the errors thrown when records don't exist and then retry failed requests. ## Implementation resources Example implementations of this multi-region database with `fly-replay` pattern can give you a head start: * [Fly Ruby gem](https://github.com/superfly/fly-ruby) for running database replicas alongside your app instances in multiple regions. * [Fly Postgres Elixir library](https://github.com/superfly/fly_postgres_elixir) for geographically distributed Elixir applications using Ecto and PostgreSQL in a primary/replica configuration on Fly.io. ## 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](/networking/dynamic-request-routing) # N-tier architecture Source: https://docs.fly.io/blueprints/n-tier-architecture What n-tier architecture is, how presentation, web, and data tiers map to Fly Machines, private networking, and Postgres, and how to deploy a multi-tier app. ![Illustration by Annie Ruygt of a bird slicing a loaf of bread-think app layers](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/get-started-n-tier.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=df7b8680260ec91e2b3cd8218a32357b) ## 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](/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](/postgres) (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](/postgres), 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 theme={null} 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](/postgres): ```bash theme={null} 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`: ```bash theme={null} 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](/postgres/create-and-connect). *** ### 3. Scale out your web tier If traffic grows, add more Machines: ```bash theme={null} fly scale count 3 ``` [Fly’s proxy](/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](/postgres) 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](/postgres) * Find out how to [scale apps on Fly](/launch/scale-count) * Read about [Fly networking](/networking) * Learn more about [n-tier architecture](https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture) # Stream logs from user apps with NATS Source: https://docs.fly.io/blueprints/observability-for-user-apps Monitor apps your users run on Fly.io: deploy the fly-telemetry forwarder, subscribe to the NATS log stream, and route each user app's logs back to that user. ![Illustration by Annie Ruygt of a printed photo depicting a bird and a ghost backpacking through a forest](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/observability.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=f80c4c4ba14f8f6dca5ee1fb881ea6e4) ## Overview When you run a platform for users to execute their own code on Fly.io—[think per-user development environments](/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](/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: ```sh theme={null} 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](/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 theme={null} 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: ```sh theme={null} 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. ![Screenshot of the NATstream open source app UI](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/natstream.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=db4afef920e51f0fe258b094f6f19152) 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] > > **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](/monitoring/logging-overview) How logs flow from your Fly apps into the platform’s NATS pipeline. * [Logs API options](/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. # Run an SSH server on a Fly Machine Source: https://docs.fly.io/blueprints/opensshd Install and run OpenSSH in your Fly.io app so rsync, scp, sshfs, VS Code Remote, Ansible, or Kamal can connect over SSH, with key setup and port 22 mapping. ![Illustration by Annie Ruygt of a terminal window, a computer and some numbers](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/openssh.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=bc4c899bb1758dc65126c81197ca10f1) ## 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/), [scp](https://en.wikipedia.org/wiki/Secure_copy_protocol), [sshfs](https://github.com/libfuse/sshfs#sshfs)), editing ([emacs](https://www.gnu.org/software/emacs/manual/html_node/emacs/Remote-Files.html), [vim](https://www.vim.org/scripts/script.php?script_id=1075), [vscode](https://code.visualstudio.com/docs/remote/ssh)), and deployment ([ansible](https://www.ansible.com/), [github actions](https://github.com/marketplace/actions/ssh-deploy), and [kamal](https://kamal-deploy.org/)). One way to use these tools is to [set up a wireguard VPN](/networking/private-networking#1-install-your-wireguard-app) and [issue a new SSH credential](/flyctl/cmd/fly_ssh_issue). This may be impractical for some use cases (example: github actions). As an alternative, you can configure and deploy an SSH server on your machine(s). This guide will walk you through the process. Before proceeding, a **caution**: unless you are **certain** that all of the clients will access this service through IPv6, you will need a [dedicated IPv4](/networking/services#dedicated-ipv4) address. ## Install and configure opensshd Most Docker images are ultimately based on Debian, so the following will work. Adjust as necessary for other operating systems (example: [Alpine](https://www.alpinelinux.org/)). ```Dockerfile theme={null} RUN apt-get update \ && apt-get install -y openssh-server \ && cp /etc/ssh/sshd_config /etc/ssh/sshd_config-original \ && sed -i 's/^#\s*Port.*/Port 2222/' /etc/ssh/sshd_config \ && sed -i 's/^#\s*PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config \ && mkdir -p /root/.ssh \ && chmod 700 /root/.ssh \ && mkdir /var/run/sshd \ && chmod 755 /var/run/sshd \ && rm -rf /var/lib/apt/lists /var/cache/apt/archives ``` Notes: * This runs `sshd` internally on port 2222. This is because by default `sshd` listens on all network interfaces, but fly.io machines already are listening on port 22 on the private network (ipv6) interface, so sshd will either complain or fail to start. Additionally, `fly deploy` will attempt to verify that your application is listening on the interfaces your application exports, and will incorrectly treat fly's listening on port 22 as evidence that your application is up and running. * Password Authentication is disabled. This avoids unnecessary prompts. We will be using SSH keys instead. * The above assumes `root` user. If your application is running under a different user, replace `/root` with the home directory of that user (example: `/home/rails`). ## Map internal port 2222 to external port 22 Add the following to `fly.toml`: ``` [[services]] internal_port = 2222 protocol = "tcp" auto_stop_machines = true auto_start_machines = true [[services.ports]] port = 22 ``` This section is needed even if the internal and external ports are the same. Notes: * `internal_port` needs to match the port you selected in the previous step. * `port` can be any available port. `22` is the [default port](https://www.ssh.com/academy/ssh/port) for SSH, and the one that most applications expect to be used. * Like with your web server port, your server can be configured to spin down when idle and restart when accessed. Feel free to adjust `auto_stop_machines` and `auto_start_machines` if your needs differ. ## Start the openssh server There are a [number of ways to run multiple processes](/app-guides/multiple-processes). The most straightforward way to start sshd before your application. Locate the `ENTRYPOINT` in your Dockerfile. If you don't have one, create a script in your application directory, name that script as the ENTYRPOINT, and make it executable. An example of such a script: ```bash theme={null} #!/bin/bash -e /usr/sbin/sshd exec "$@" ``` Note: the above needs to be run as root. If your Dockerfile specifies another `USER`, you can work around this by installing and configuring `sudo` and then removing sudo access before running your main process. For example, if your userid is `rails`, you would add the following to your Dockerfile: ```Dockerfile theme={null} RUN apt-get install -y sudo && \ echo "%rails ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers ``` And then update your entrypoint script: ```bash theme={null} sudo /usr/sbin/sshd sudo sed -i "/^%rails/d" /etc/sudoers ``` The above commands will start `sshd` as root, then remove sudo access from your unprivileged userid. ## Upload your SSH key First, locate your SSH key. You can create a new key using [`ssh-keygen`](https://www.ssh.com/academy/ssh/keygen), Or you can use an existing one: look inside the `.ssh` folder in your home directory for a file with a name like `id_rsa.pub`. Once located, there are multiple ways to proceed. Following you will find two ways. Depending on the framework your application uses, you may have other ways to store credentials or environment variables. As this step only deals with public keys, one need not take extraordinary measures to prevent leakage. ### Alternative 1: Set and use a secret ```bash theme={null} fly secrets set "AUTHORIZED_KEYS=$(cat ~/.ssh/id_rsa.pub)" ``` Powershell users will want to use the following instead: ```powershell theme={null} fly secrets set "AUTHORIZED_KEYS=$(Get-Content $HOME\.ssh\id_rsa.pub)" ``` Next, update your entrypoint script to contain the following: ```bash theme={null} echo $AUTHORIZED_KEYS > /root/.ssh/authorized_keys ``` If you are running with sudo, you would want to do the following instead: ```bash theme={null} echo $AUTHORIZED_KEYS | sudo tee /root/.ssh/authorized_keys > /dev/null ``` ### Alternative 2: Copy from a volume If you have a volume and if you can arrange to upload the file there, you can directly copy it as a part of your entrypoint script. ```bash theme={null} cp /volume/.ssh/authorized_keys /root/.ssh ``` ## \[RECOMMENDED] Make SSH host keys stable The first time you SSH into a server you will be presented with a fingerprint for the server you are accessing. If you accept that fingerprint, it will be added to your `known_hosts` file in your `.ssh` directory. This key is generated when you install `openssh-server`. The issue arises when you redeploy your application. If something changes (or your docker cache expires), the installation of `openssh-server` may be rerun, and new keys will be generated. To avoid these keys from being used, you can capture and restore the keys. The following assumes that you have a [volume](/launch/volume-storage) mounted at `/volume`, and an entrypoint script. ```bash theme={null} mkdir -p /volume/.ssh if [[ "$(ls /volume/.ssh/*_key)" = "" ]]; then cp /etc/ssh/*_key /volume/.ssh else cp /volume/.ssh/*_key /etc/ssh fi ``` Notes: * These keys are expected to be private, so if you go with an alternate route, make sure that these values are not committed to a public repository unencrypted. * If you are running with `sudo`, you need to add `sudo` before the `mkdir`, the `ls` and both of the `cp` statements. * If you have multiple machines, you may want all of them to [share the same keys](https://security.stackexchange.com/a/89621). ## \[OPTIONAL] Configure client user and aliases Your full dnsname may be a mouthful, the user the application runs under may be different than the one you use on your laptop. the port you expose may be non-standard, or you may have multiple machines and a desire to be able to SSH into a specific one. If any of these apply to you, you can create or update a file named [`config`](https://www.ssh.com/academy/ssh/config) in your `.ssh` directory. Following is an example that illustrates addressing a number of the above cases: ```config theme={null} Host appname Hostname appname.fly.dev User rails Port 2222 Host *.appname HostName %h.internal ProxyJump rails@appname.fly.dev:2222 User rails Port 2222 ``` Additionally, if you want to avoid the fingerprint checking, you can add the following to each of the `Host` entries: ```config theme={null} StrictHostKeyChecking no ``` ## Related reading * [`flyctl` SSH commands](/flyctl/cmd/fly_ssh) Official reference for how to use `fly ssh`/`fly ssh sftp` to access running machines. * [Multiple processes inside a Fly.io app](/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. # Per-User Dev Environments with Fly Machines Source: https://docs.fly.io/blueprints/per-user-dev-environments Host isolated dev or code-execution environments for each user on Fly Machines, with one app per user, optional volumes, and a fly-replay router for subdomains. ![Illustration by Annie Ruygt of different envrionments under glass domes](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/per-user-dev.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=53d02349459836c36142c785e7ab84cc) ## Overview Fly Machines are fast-launching VMs behind [a simple API](/machines/api), enabling you to launch tightly isolated app instances in milliseconds [all over the world](/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](/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](/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](/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](/volumes). ### Example Architecture Diagram ![Diagram showing router app directing traffic to user apps containing Fly Machines with volumes](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-sandbox-architecture.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8a2e776960d7a921990fd52fc4e33b74) ### 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](/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](/networking/dynamic-request-routing#replay-caching) to further reduce latency and load on the router app. * Make sure you've added [a wildcard domain](/networking/custom-domain#add-your-domain-to-your-app) (\*.example.com) to your router app (read more about the [Certificates API reference](/machines/api/certificates-resource)). ### 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](/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](/machines/api/machines-resource) and the [Volumes API resource](/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](/tigris) 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. * **One app per user:** Putting all user Machines into a single app and routing to them with dynamic routing works, but `auto_stop` won't behave the way you expect once you scale. The Fly Proxy's stop loop is rate-limited: it [stops or suspends at most one Machine per region each pass, and runs every few minutes](/reference/fly-proxy-autostop-autostart#fly-proxy-process-to-stop-or-suspend-machines). That's fine for a normal app; with thousands of Machines the loop can't keep up, and most of your idle Machines stay running. Machines in a single app also share app-level secrets and a flat [private network](/networking/private-networking). A compromised user environment can reach every other Machine in the app. If you do keep all user Machines in one app, implement stop-when-idle behavior in your app or orchestrator (see [apps that shut down when idle](/launch/autostop-autostart#apps-that-shut-down-when-idle)). Don't rely on the Fly Proxy to keep most Machines stopped. ## Related reading * [Connecting to User Machines](/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](/machines/guides-examples/multi-container-machines) When your per‑user environment needs sidecars (logs, metric exporters, agent processes) alongside the main service. # Run private apps with Flycast Source: https://docs.fly.io/blueprints/private-applications-flycast Deploy a private HTTP service reachable only on your org's network with Flycast on Fly.io, routed by Fly Proxy so stopped Machines wake on demand. ![Illustration by Annie Ruygt of some apps sitting around a campfire](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/campfire_apps.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=c362419a964c82006c24f4ccc074aec5) ## 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. Sometimes you need a middle ground between fully public apps and fully private apps. [Flycast](/networking/flycast) addresses are private but global IPv6 addresses inside your private network that go through the Fly Proxy, so you get all of the load management and Machine waking powers that you get for free with public apps. This blueprint covers what Flycast is, when and why you’d want to use it, and shows you how to deploy a small private HTTP service that you can connect to over Flycast. ## What is Flycast? Before we get started, let’s talk about Flycast and when you would want to use it. In general we can split every kind of Fly App into two categories: public apps and private apps. A public app is what you’d expose to the public Internet for your users. These are usually hardened apps that allow users to do some things, but have access limitations that prevent them from stepping outside their bounds. These are mostly programs that listen over HTTP for browsers to interact with. Your users connect to a public app through the platform router via the .fly.dev domain or whatever other domain you’ve set up. A private app is something internal, like a database server or a worker queue. These are things that run in the background and help you get things done, but are intentionally designed to NOT be exposed to the public Internet. You wouldn’t want to expose your Postgres or Valkey servers to anyone, would you? However, with a fully private app, all connections go directly to the Machines via their .internal addresses, so you have to keep them running 24/7 to maintain connectivity. This is fine for services like database engines where you want them to be running all the time, but what about an admin panel? You want your admin panel to be separate from your main app so that users can ever get into it, even by accident, but you also want it to shut down when it’s not in use. Flycast exists for this middle category of apps. With Flycast, your apps are only visible over your organization’s private network, but any traffic to them goes through the proxy so they can turn on when you need them and turn off when you don’t. This allows your administrative panels to be physically separate so that users can’t access them. When you want to connect to an app via Flycast, you connect to `appname.flycast`. ### Security note Just a heads-up. In general, it’s a bad idea to assume that network access barriers like Flycast or NAT are security layers. At best, this is an obfuscation layer that makes it more difficult for attackers to get into private applications. Flycast is not a replacement for authentication in your private applications. With Flycast, you don’t know *who* a request is coming from, but you do know that it’s coming from *something* or *someone* in your private network. One of the biggest platform features that uses Flycast out of the box is [Managed Postgres](/postgres). Even though Flycast addresses are local to your private network, Managed Postgres still configures usernames and passwords for your database. ## Goal We'll show Flycast off by deploying a small HTTP service that's only reachable from inside your organization's private network. The classic example is an internal admin panel: a service you and your apps need to reach, but that should never be exposed to the public internet. Flycast also lets the platform turn the service off when nobody's using it, so you're not paying to keep an idle admin panel running. For this walkthrough we'll use the public [`nginxdemos/hello`](https://hub.docker.com/r/nginxdemos/hello) image as a stand-in for any private HTTP service. It's a tiny container that responds to HTTP requests with the server's hostname and address, which makes it easy to confirm that traffic is reaching the right Machine over Flycast. ## Prerequisites To get started, you need to do the following: * [sign up or sign in](/getting-started/sign-up-sign-in) to Fly.io * [install flyctl](/flyctl/install) (the Fly CLI) If you want to interact with your Flycast apps from your computer, you’ll need to [jack into your private network with WireGuard](/blueprints/connect-private-network-wireguard). ## Steps Create a new folder on your computer called `flycast-demo` and open a terminal in it. We don't need any source code for this walkthrough. We'll launch the app directly from a public Docker image with the `--flycast` flag, which tells Fly Launch to allocate a private IPv6 address instead of public ones. We'll also pass `--no-deploy` so we can adjust one setting before the first deploy: ``` fly launch --image nginxdemos/hello --flycast --internal-port 80 --no-deploy ``` The name you choose for your app will be the hostname you use to reach it over Flycast (`.flycast`). When `fly launch` asks if you want to tweak the settings, you can accept the defaults. The `--internal-port 80` flag tells Fly Launch that our app listens on port 80 (the default for the `nginxdemos/hello` image). Fly Launch defaults to port 8080, so without this flag the Fly Proxy wouldn't be able to reach the app. You could also set `internal_port = 80` in `fly.toml` directly in the next step; the flag is just a shortcut so you don't have to edit the file twice. Open the generated `fly.toml` in your editor, find the `[http_service]` block, and change `force_https` to `false`: ```toml theme={null} [http_service] internal_port = 80 force_https = false ``` Fly Launch enables `force_https` by default, which makes the proxy return a 301 redirect from HTTP to HTTPS. That's a good default for public apps, but Flycast addresses don't have public TLS certificates, so the redirect would just break plain HTTP requests from inside your private network. Now deploy the app: ``` fly deploy ``` After deploy finishes, you can see the list of IP addresses associated to an app with `fly ips list`: ``` $ fly ips list VERSION IP TYPE REGION CREATED AT v6 fdaa:3:9018:0:1::7 private global 23h12m ago ``` Learn more about [Fly.io public, private, shared and dedicated IP addresses](/networking/services#anycast-ip-addresses). This app only has one IP address: a private Flycast IPv6 address. If it had public IP addresses, it'd look like this: ``` $ fly ips list -a recipeficator VERSION IP TYPE REGION CREATED AT v6 2a09:8280:1::37:7312:0 public (dedicated) global May 30 2024 13:51 v4 66.241.124.113 public (shared) Jan 1 0001 00:00 ``` Now that we've proven it's private, let’s open an interactive shell Machine to play around with Flycast. Create the shell Machine with `fly machine run`, giving it enough memory to install a few packages with `apt`: ``` $ fly machine run --shell --vm-memory 1024 ubuntu root@e784127b51e083:/# ``` The Ubuntu image we chose is very minimal, so we need to install a few tools such as ping, curl, and dig: ``` # apt update && apt install -y curl iputils-ping dnsutils ``` Say my app is named `flycast-demo`. Let’s look up its `.flycast` address with `nslookup flycast-demo.flycast`: ``` # nslookup flycast-demo.flycast Server: fdaa::3 Address: fdaa::3#53 Name: flycast-demo.flycast Address: fdaa:3:9018:0:1::7 ``` It matches that IP address from earlier. Now let’s see what happens when we ping it: ``` # ping flycast-demo.flycast -c2 PING flycast-demo.flycast (fdaa:3:9018:0:1::7) 56 data bytes 64 bytes from fdaa:3:9018:0:1::7: icmp_seq=1 ttl=63 time=0.138 ms 64 bytes from fdaa:3:9018:0:1::7: icmp_seq=2 ttl=63 time=0.223 ms --- flycast-demo.flycast ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1009ms rtt min/avg/max/mdev = 0.138/0.180/0.223/0.042 ms ``` Now let’s make a request to the app with curl: ``` # curl http://flycast-demo.flycast ``` You should get back an HTML page with the server's hostname and address. Wait a few moments so your app goes to sleep, then run the `time` command to see how long the first request takes: ``` # time curl -o /dev/null -s http://flycast-demo.flycast real 0m1.539s user 0m0.004s sys 0m0.012s ``` It took a couple of seconds for the platform to wake the Machine up and make sure it was ready for your request. The next request is a lot faster: ``` # time curl -o /dev/null -s http://flycast-demo.flycast real 0m0.018s user 0m0.003s sys 0m0.008s ``` And if you wait a few moments, it’ll spin back down. And there we go! We’ve covered what Flycast is, why you’d want to use it, and walked through deploying a private HTTP service to show it off. The same pattern works for any internal app: admin panels, internal APIs, dashboards, or anything else you'd rather keep off the public internet. ## Related reading * [Autostart and autostop private apps](/blueprints/autostart-internal-apps) * [Flycast: private Fly.io services](/networking/flycast) * [Private networking](/networking/private-networking) # Remote MCP Server Hosting: Deploy MCP Servers on Fly.io Source: https://docs.fly.io/blueprints/remote-mcp-servers Host a remote MCP server on a Fly Machine that any MCP client can reach over HTTP and that stops when idle, as a multi-tenant app or one app per user. ![Illustration by Annie Ruygt of bird working at their computer while drinking coffee](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/remote-mpc.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=96f7e564ba49339476389712d485b0ae) ## Overview Hosting a remote MCP server on Fly.io means running it on a Fly Machine, a fast-booting isolated VM that any MCP client can reach over HTTP, and that can stop when idle and start again on the next request. The Model Context Protocol (MCP) gives LLMs new powers. It was originally developed by Anthropic and is now supported by most major LLM clients. 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](/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](/blueprints/per-user-dev-environments)). ## Multi-tenant MCP Servers ![Diagram showing multi-tenant MCP server architecture on Fly.io](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-mcp-multi-tenant.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=0d7ce289eff01e5b057dc958211040bc) 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](/flyctl/cmd/fly_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](/flyctl/cmd/fly_mcp) and [the community forum](https://community.fly.io/t/running-mcps-on-and-with-fly-io/24588)). ## Single-tenant MCP Servers ![Diagram showing single-tenant MCP server architecture on Fly.io](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-mcp-single-tenant.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=15bdb11ec160a009e0a019f9a8d46409) 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](/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](/flyctl/cmd/fly_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](/flyctl/cmd/fly_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](/model-context-protocol) Learn more in our reference for MCP: what it is, how it works, and how to deploy servers. * [Connecting to User Machines](/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](/flyctl/cmd/fly_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. # High availability: run multiple Machines Source: https://docs.fly.io/blueprints/resilient-apps-multiple-machines A Fly Machine runs on one host and won't move if that host fails. Run at least two Machines per process, using autostop/autostart or standbys to limit cost. ![Illustration by Annie Ruygt of an armoured truck with a Fly balloon logo](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/resilient-apps.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=9788df15edf16391245bd2bc3dc1698d) > [!NOTE] > > **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](/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. ### You get two Machines on first deploy When you deploy an app for the first time with the `fly launch` or `fly deploy` command, you automatically get two identical Machines for processes that have HTTP/TCP services configured in `fly.toml`. The Machines have autostop/autostart enabled so that Fly Proxy can start and stop them based on traffic to your app. You'll also get this default redundant Machine when you `fly deploy` after scaling to zero. > [!NOTE] > > **Volumes:** You'll only get one Machine with `fly launch` for processes or apps with volumes mounted. Volumes don't automatically replicate your data for you, so you'll need to set that up before intentionally creating more Machines with volumes. ### Add more Machines yourself If your app doesn't already have multiple Machines with autostop/autostart configured, then you can set it up yourself. You can create any number of Machines to both meet user demand and provide redundancy against host failures. #### Set up autostop/autostart Use [Fly Proxy autostop/autostart](/launch/autostop-autostart) to automatically stop and start Machines based on traffic. Keep one or more Machines running in your primary region if you want to. Example `fly.toml` config: ```toml theme={null} [http_service] internal_port = 8080 force_https = true auto_stop_machines = "stop" # Fly Proxy stops Machines based on traffic auto_start_machines = true # Fly Proxy starts Machines based on traffic min_machines_running = 0 # No. of Machines to keep running in primary region [http_service.concurrency] type = "requests" soft_limit = 200 # Used by Fly Proxy to determine Machine excess capacity ``` Fly Proxy uses the concurrency `soft_limit` to determine if Machines have excess capacity. Learn more about [how Fly Proxy autostop/autostart works](/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](/machines/api/machines-resource#machine-config-object-properties) in your `create` or `update` calls. #### 2. Create more Machines Create extra Machines, or even just one extra Machine, using flyctl: ``` fly scale count 2 ``` Or deploy even more Machines in more than one region: ``` fly scale count 20 --region ams,ewr,gig ``` Learn more about [scaling the number of Machines](/launch/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](/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](/apps/app-availability#standby-machines-for-process-groups-without-services). ### You get a standby Machine on first deploy When you deploy an app for the first time with the `fly launch` or `fly deploy` command, you automatically get a standby Machine for processes that don't have services configured in `fly.toml` (and therefore aren't visible to Fly Proxy). That standby Machine is a copy of the original, running Machine. You'll also get this standby Machine when you `fly deploy` after scaling to zero. ### Create a standby Machine yourself If you don't already have a standby Machine for your worker Machines, then you can create one yourself. To create a standby Machine that will take over the same tasks when a worker Machine is down, use this one command to clone the worker Machine and make the clone a standby: ``` 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](/machines/api/machines-resource#machine-config-object-properties). ### 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. To create a standby Machine from a different image: ``` fly machine run --standby-for ``` *** ## 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. 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. 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. ## Related reading 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](/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](/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](/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](https://fly.io/ruby-dispatch/run-ordinary-rails-apps-globally/): Project-level example of Rails distributed across regions with smart routing. # Git Branch Preview Environments on Github Source: https://docs.fly.io/blueprints/review-apps-guide Deploy ephemeral review apps for every pull request using a GitHub Actions workflow and the fly-pr-review-apps Action on Fly.io, with custom specs and secrets. ![Illustration by Annie Ruygt of Github Octocat and Frankie with some apps](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/review-apps-github.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=76b27694325059c63aa6b555b5d4ce32) ## 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 If you've worked with GitHub Actions before and want to jump right in, here are the steps to take: 1. Create a new repository secret on your GitHub repository called `FLY_API_TOKEN` and provide it with the returned value of `fly tokens org ` 2. Create a new workflow in your project at `.github/workflows/fly-review.yml` and add the following [YAML code](https://gist.github.com/anniebabannie/3cb800f2a890a6f3ed3167c09a0234dd). 3. Commit and push the changes containing your GitHub workflow. And that's it! Any new pull requests will trigger the creation of review apps for your application. By default, review apps will be available at `https://pr---.fly.dev`. The workflow described above will spin up a **single application** without other resources; if data stores, multiple Fly Apps, or other resources are required, see [Customizing your workflow](#customize-your-workflow). For more details about each step, follow the guide below. ## Setting up review apps on Fly.io ### Set your Fly API token Your GitHub Action will need a Fly API token to deploy and manage new review apps on Fly.io. You can get this token using the following [flyctl](/flyctl/install) command: ```bash theme={null} fly tokens org ``` This token is specific to a single Fly.io organization and is valid for applications belonging the that organization. Next, let's save our API token as a secret in our GitHub repository. Visit the Settings tab in your repository, then Secrets and variables → Actions. Alternatively, you can visit `https://github.com///settings/secrets/actions`. There, create a new **Repository Secret** called `FLY_API_TOKEN` with the value of your token. ### Create your GitHub Action Next, let's create a new GitHub Action by creating a new YAML file in your app's repository for our workflow: ```bash theme={null} touch .github/workflows/fly-review.yml ``` Then add the following to your new `fly-review.yml` file: ```yaml theme={null} name: Deploy Review App on: # Run this workflow on every PR event. Existing review apps will be updated when the PR is updated. pull_request: types: [opened, reopened, synchronize, closed] env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} # Set these to your Fly.io organization and preferred region. FLY_REGION: iad FLY_ORG: personal jobs: review_app: runs-on: ubuntu-latest outputs: url: ${{ steps.deploy.outputs.url }} # Only run one deployment at a time per PR. concurrency: group: pr-${{ github.event.number }} # Deploying apps with this "review" environment allows the URL for the app to be displayed in the PR UI. # Feel free to change the name of this environment. environment: name: review # The script in the `deploy` sets the URL output for each review app. url: ${{ steps.deploy.outputs.url }} steps: - name: Get code uses: actions/checkout@v4 - name: Deploy PR app to Fly.io id: deploy uses: superfly/fly-pr-review-apps@1.2.1 ``` In the `deploy` step, `superfly/fly-pr-review` contains the custom Action with a shell script that creates and deploys your review apps. If you're curious, check out [entrypoint.sh](https://github.com/superfly/fly-pr-review-apps/blob/main/entrypoint.sh) for full details on how the script works. ### Commit and push your workflow Finally, commit and push your changes to your GitHub repository. Once pushed, any new PRs will spin up a review app for you to preview your code on Fly.io. ## Customize your workflow Often our applications do not operate in isolation and require other resources to function. The sample workflow mentioned above spins up a stand-alone Fly App, but you can customize your `fly-review.yml` GitHub workflow to create additional Fly Apps and resources, such as a Redis, Memcached, etc. You can find examples of these in the [README](https://github.com/superfly/fly-pr-review-apps/tree/main) for `superfly/fly-pr-review-apps`. ### Customize with inputs The `superfly/fly-pr-review-apps` custom GitHub Action also provides a number of **inputs** that can be added to tweak behavior. See the [README](https://github.com/superfly/fly-pr-review-apps/tree/main) for the complete list. These inputs can be added to the `deploy` step in your workflow using the `with` key, like so: ```yaml theme={null} # fly-review.yml # ... steps: # ... - name: Deploy PR app to Fly.io id: deploy uses: superfly/fly-pr-review-apps@1.2.1 with: name: my-app-name-pr-${{ github.event.number }} config: fly.review.toml ``` ### Defining resource specs You probably won't need the same specifications for the Fly Machines used by review apps as you do for your production app. There are two ways to override the resources used in review apps. **1. Specifying a different `config` file:** By default, review apps will use the `fly.toml` in your application root, but you can create a separate TOML file with different specs using the `config` input, like so: ```yaml theme={null} # fly-review.yml # ... steps: # ... - name: Deploy PR app to Fly.io id: deploy uses: superfly/fly-pr-review-apps@1.2.1 with: config: fly.review.toml ``` **2. Setting specs in GitHub Action inputs:** Alternatively, you can set the specs for the Fly Machines used by your review apps using the following inputs: * `vmsize` * `cpu` * `cpukind` * `memory` * `ha` ### Default secrets and environment variables We recommend setting default values for any secrets or environment variables your review apps require so you don't have to set them individually. You can do this by defining each secret as either a **repository secret** *or* an **environment secret** in your GitHub repo. If using environment secrets, these should be set on the environment used in your workflow, such as `review`. Once you have your secrets and environment variables set in GitHub, add each of them to your `fly-review.yml` workflow, separated by a space, like so: ```yaml theme={null} # fly-review.yml # ... steps: # ... - name: Deploy PR app to Fly.io id: deploy uses: superfly/fly-pr-review-apps@1.2.1 with: secrets: FOOBAR=${{ secrets.FOOBAR }} SOME_API_KEY=${{ secrets.SOME_API_KEY}} ``` ## Related Reading * [Continuous Deployment with Fly.io and GitHub Actions](/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](/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. # Roll back a deploy Source: https://docs.fly.io/blueprints/rollback-guide Undo a bad Fly.io deploy: find a previous release image with fly releases --image, redeploy it, tag images clearly, and handle database migrations separately. ![Illustration by Annie Ruygt of a Frankie the hot air balloon helping his developer friend roll back a bad deploy](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/rollback-guide.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=c69217e36c2325fd849d154732241864) ## 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 theme={null} 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 theme={null} 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 theme={null} fly deploy --image-label v1.2.3 ``` That command: 1. Builds your app 2. Tags the image as `v1.2.3` 3. Deploys it Rolling back later becomes: ```bash theme={null} 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](/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](/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 theme={null} 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 theme={null} 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 theme={null} 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](/blueprints/seamless-deployments) How to set up deploys that avoid downtime and make rollbacks less painful. * [Using the Fly Docker Registry](/blueprints/using-the-fly-docker-registry) Details on image tagging and retention — key context for understanding how rollbacks work. * [Staging and Production Isolation](/blueprints/staging-prod-isolation) Tips on environment isolation that reduce the blast radius of a bad release. # Zero-downtime deployments Source: https://docs.fly.io/blueprints/seamless-deployments Get zero-downtime deploys on Fly.io with health checks that work, a rolling, canary, or bluegreen strategy, and release_command for migrations. ![Illustration by Annie Ruygt of a balloon doing a health check](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/seamless-deployments.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=00d5c1cd8decc92447a74c44e9fa3af2) ## Overview > [!NOTE] > > **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](/reference/fly-proxy) is watching your Machines and only sending them traffic if they pass your health checks. [Health checks](/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](/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](/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`](/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](/reference/health-checks) * [Deploy an App](/launch/deploy) * [App Configuration (`fly.toml`)](/reference/configuration) # Setting Hard and Soft Concurrency Limits on Fly.io Source: https://docs.fly.io/blueprints/setting-concurrency-limits Tune soft_limit and hard_limit for Fly Machines by load testing a copy of your app, then let autostop/autostart scale within those limits. ![Illustration by Annie Ruygt of Frankie the hot air balloon with two other balloon friends of different colors](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/setting-limits.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=ae6edfa4ef3fe26d3425ca0cb2b2e474) ## 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](/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`](/reference/configuration#http_service-concurrency) Check out this section of the `fly.toml` config reference explaining concurrency parameters. * [Load Balancing on Fly.io](/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. # Shared Nothing Architecture Source: https://docs.fly.io/blueprints/shared-nothing Scale a Rails app as a shared-nothing monolith on Fly.io, with per-tenant SQLite on volumes, Phusion Passenger, Fly-Replay routing, and backups. ![Illustration by Annie Ruygt of a number of couples dancing, including a two clones dancing with separate partners.](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/dancing-clones-cover_sm.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=b184a83198ef7e0daa379567926e6b69) [YouTube video player](https://www.youtube-nocookie.com/embed/_cT4Unk5RiU) > [!NOTE] > > 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. Showcase (that’s what I called it) started out as a Ruby on Rails app and a database; it kept track of competitors and their schedules for a single Showcase (that’s what these ballroom dance events are called). I ran it on a single Mac Mini in my home. Over time, it grew out of my attic and onto VMs in the cloud. It supports more events in different cities, and does more things. Conventional wisdom is that as you grow, you make each type of service reusable and put it into a separate set of machines. If this approach isn’t the best one for your app, throw it out the window! I did. In my case, it made a lot of sense to put all the services into a single VM, and scale out by repeating that set into new VMs. This works great for me. Maybe it’s right for your app, too! Maybe not! Prepare to bathe in the gory details of my stateful, serverful, shared-nothing monolith. ## Shared-nothing architectures > [!NOTE] > > A shared-nothing architecture is a distributed computing architecture in which each update request is satisfied by a single node (processor/memory/storage unit) in a computer cluster. As you might expect, a Showcase competition happens live and in a single location. Very few people need access to the system, and they’re often all in the same room; picture a few judges entering scores at the end of a number—that’s your peak simultaneous users. Data’s not shared between events–they don’t want or need to share a database and Redis cache. Shared-nothing architectures are nothing new. An excerpt from [Wikipedia](https://en.wikipedia.org/wiki/Shared-nothing_architecture): > The intent is to eliminate contention among nodes. Nodes do not share (independently access) the same memory or storage. One alternative architecture is shared everything, in which requests are satisfied by arbitrary combinations of nodes. This may introduce contention, as multiple nodes may seek to update the same data at the same time. While serverless is one kind of a shared-nothing architecture - this post explores another variation. One where servers are explicitly deployed, users are assigned to servers, and users have databases that aren't shared. This application started, like most applications, small. And that's where the story begins. ## A single event > [!NOTE] > > A Fly.io machine is not merely a Docker container, but rather a [full VM](https://fly.io/blog/docker-without-docker/). Treat it as such: run as many services as you like on a single machine. My first showcase was in Harrisburg, Pennsylvania. The app I developed initially supported only that one event, and all of the services needed to support the event ran on a single machine. Like most Rails applications, this consisted of a Rails application, a database, and (eventually) Redis. Over time a number of supporting services were added (NGINX, opensshd, rsyncd, and a small log "heartbeat" script). Today, these all happily run on a single Fly.io machine. Conventional wisdom is that as you grow you put each type of service into a separate set of machines. That isn't always the right approach. An excerpt from DHH's [Majestic Monolith](https://signalvnoise.com/svn3/the-majestic-monolith/): > 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?) 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. There are a lot of [options for running multiple processes inside a Fly.io App](/app-guides/multiple-processes). I use a combination of [ENTRYPOINT](https://github.com/rubys/showcase/blob/main/bin/docker-entrypoint) running a [deploy](https://github.com/rubys/showcase/blob/main/bin/deploy) script and a [procfile](https://github.com/rubys/showcase/blob/main/Procfile.fly). Given the load for each event is only for one customer, I only need 1 CPU per server. While CPU is not a concern, it still is important to size the amount of RAM needed. Even with all of the services running, according to [Graftana/Fly Metrics](https://fly-metrics.net/), each instance uses about 220MB of RAM when idle, and grows to 450MB when active. That being said, I have seen machines fail due to [OOM](https://en.wikipedia.org/wiki/Out_of_memory) with only 512MB, so I give each machine 1GB. I also define an additional 2G of [swap](/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](/networking/private-networking) that lets you VPN into your own private network of machines. ## Multi-tenancy > [!NOTE] > > The code base supports only a single event. Running multiple events on a single machine is the next step before jumping into multiple machines. There are a number of blog posts out there on how to do [multi-tenancy](https://blog.arkency.com/comparison-of-approaches-to-multitenancy-in-rails-apps/) with Rails. [Phusion Passenger](https://www.phusionpassenger.com/) provides a [different approach](https://stackoverflow.com/questions/48669947/multitenancy-passenger-rails-multiple-apps-different-versions-same-domain). For this use case, the Phusion Passenger approach is the best match. The database for a mid-size local event is about a megabyte in size (about the size of a single camera image), and can be kept in SQLite. Passenger provides a [`passenger_min_instances`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_min_instances) `0` option that allow a reasonable number of past, present, and future events to be hosted essentially without any overhead when not in use. This does mean that you have to accept the cold start times of the first access, but that appears to be two seconds or less on modern hardware, which is acceptable. The NGINX configuration file defines a set of environment variables for each tenant to control the name of such things as the database, the log file, base URL, and pidfile. For web sockets (Action Cable), NGINX is [preferred over Apache httpd](https://www.phusionpassenger.com/library/config/apache/action_cable_integration/). The [documentation for Deploying multiple apps on a single server (multitenancy)](https://www.phusionpassenger.com/library/deploy/nginx/) is 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 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**. * Instead, use environment variables to stream from, and broadcast to, different action cable channels. The end result is what outwardly appears to be a single Rails app, with a single set of assets and a single cable. One additional rails instance serves the index and provides a global administration interface. Recapping: a single server can host multiple events, each event is a separate instance of the same Rails application with a separate SQLite database placed on a volume. The server is always on (`auto_stop_machines = false`), but the Rails apps are spun up when needed and spin down when idle (using [`passenger_pool_idle_time`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_pool_idle_time)) ## Multiple regions > [!NOTE] > > Distributing an app across multiple regions not only lets you service requests close > to your users, it enables horizontal scalability and isolation. While most of my users are US based, my showcase app now has a second user in Australia, and now one in Warsaw, Poland. Sending requests and replies half way around the world unnecessarily adds latency. As each machine is monolithic and self-contained, replicating service into a new region is a matter of creating the new machine and routing requests to the correct machine. This application use three separate techniques to route requests. The first technique is to [listen](https://github.com/rubys/showcase/blob/main/app/javascript/controllers/region_controller.js) for [turbo:before-fetch-requests](https://turbo.hotwired.dev/reference/events#http-requests) events and insert a [fly-prefer-region header](/networking/dynamic-request-routing#the-fly-prefer-region-header). The region is extracted from a data attribute added to the [body](https://github.com/rubys/showcase/blob/aae08a6d57f92335b2cdbb94756e5416b7b50f83/app/views/layouts/application.html.erb#L16) tag. This all made possible by how [Turbo works](https://turbo.hotwired.dev/handbook/introduction#turbo-drive%3A-navigate-within-a-persistent-process): > This happens by intercepting all clicks on `` links to the same domain. When you click an eligible link, Turbo Drive prevents the browser from following it, changes the browser’s URL using the History API, requests the new page using fetch, and then renders the HTML response. The same source above also defines a `window.inject_region` function to inject the header in other places in the code that may issue fetch requests. The next (and final) two approaches are done in the NGINX configuration itself. This configuration is defined at startup on each machine. Here is an example of the definition for `raleigh` on machines in regions other than `iad`: ``` ## Raleigh location /showcase/2024/raleigh/cable { add_header Fly-Replay region=iad; return 307; } location /showcase/2024/raleigh { proxy_set_header X-Forwarded-Host $host; proxy_pass http://iad.smooth.internal:3000/showcase/2024/raleigh; } ``` This replays action cable (web socket) requests to the correct region, and [reverse proxies](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) all other requests to the correct machine using its [`.internal` address](/networking/private-networking#fly-io-internal-dns). The latter is done because [fly-replay](/networking/dynamic-request-routing#requirements-and-limitations) is limited to 1MB requests, and uploading audio files may exceed this limit. ## Appliances > [!NOTE] > > For larger tasks, try > decomposing parts of your applications into event handlers, starting up Machines to handle requests when needed, and stopping them when the requests are done. While above I've sung the praises of a Majestic Monolith, there are limits. Usage of this application is largely split into two phases: a lengthy period of data entry, followed by a brief period of report generation. This pattern then repeats a second time as scores are entered and then distributed. Report generation is done using [puppeteer](https://pptr.dev/) and [Google Chrome](https://www.google.com/chrome/). While this app runs comfortably with 1MB of RAM, I've had Google Chrome crash machines that have 2MB. Generating a PDF from a web page (including authentication) is something that can be run independently of all of the other services of your application. By making use of [`auto_stop_machines` and `auto_start_machines`](/reference/configuration#the-http_service-section), a machine dedicated to this task can be spun up in seconds and perform this function. In my case, no changes to the application itself is needed, just a few lines of NGINX configuration: ``` ## PDF generation location ~ /showcase/.+\.pdf$ { add_header Fly-Replay app=smooth-pdf; return 307; } ``` This was originally published as [Print on Demand](https://fly.io/blog/print-on-demand/), and the code for that article can be found on [GitHub as fly-apps/pdf-appliance](https://github.com/fly-apps/pdf-appliance). This architectural pattern can be applied whenever there is a minority of requests that require an outsized amount of resources. A second example that comes to mind: I've had requests for audio capture and transcription. ## Backups > [!NOTE] > > Running databases on a single volume attached to a single machine is a single > point of failure. It is imperative that you have recent backups in case of failure. At this point, databases are small, and can be moved between servers with a simple configuration change. For that reason, for the moment I've elected to have each machine contain a complete copy of all of the databases. To make that work, I [install](https://github.com/rubys/showcase/blob/aae08a6d57f92335b2cdbb94756e5416b7b50f83/Dockerfile#L67), [configure](https://github.com/rubys/showcase/blob/main/Dockerfile#L114-L137), and [start](https://github.com/rubys/showcase/blob/aae08a6d57f92335b2cdbb94756e5416b7b50f83/bin/deploy#L26-L27) [rsync](https://rsync.samba.org/). With Passenger, I set [`passenger_min_instances`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_max_pool_size) to zero, allowing each application to completely shutdown after five minutes of inactivity. I define a [`detached_process`](https://www.phusionpassenger.com/library/indepth/hooks.html#detached_process) hook to run a [script](https://github.com/rubys/showcase/blob/main/bin/passenger-hook). As that hook will be run after each process exits, what it does first is query the number of running processes. If there are none (not counting websockets), it begins the backup to each Fly.io machine using `rsync --update` to only replace the newer files. After that completes, a webhook is run on an offsite machine (a mac mini in my attic) to rsync all files there. In order for that to work, I run [an ssh server](/blueprints/opensshd) on each Fly.io machine. And I don't stop there: * My home machine runs the same passenger application configured to serve all of the events. This can be used as a hot backup. I've never needed it, but it comforting to have. * I also rsync all of the data to a [Hetzner](https://www.hetzner.com/) which runs the same application. * Finally, I have a cron job on my home machine that makes a daily backup of all of the databases. A new directory is created per day, and [hard links](https://en.wikipedia.org/wiki/Hard_link) are used to link to the same file in the all too common case where a database hasn't changed during the course of the day. As this is in a terabyte drive, I haven't bothered to prune, so at this point, I have daily backups going back years. While this may seem like massive overkill, always having a local copy of all of the databases to use to reproduce problems and test fixes, as well as having a complete instance that I can effectively use as a staging server alone makes all this worth it. Finally, since each region contains a copy of all databases, I use [`auto_extend_size_threshold`](/reference/configuration#auto_extend_size_threshold) and [`auto_extend_size_increment`](/reference/configuration#auto_extend_size_increment) to ensure that I never run out of space. ## Logging > [!NOTE] > > It is important that logs are there when you need them. For this reason, logs need both monitoring and redundancy. Equally as important as backups are logs. Over the course of the life of this application I've had two cases where I've lost data due to errors on my part and was able to reconstruct that data by re-applying POST requests extracted from logs. The biggest problem with logs is that you take them for granted. You see that they work and forget about them, and there always is the possibility that they are not working when you need them. I've written about my approach in [Multiple Logs for Resiliency](https://fly.io/blog/redundant-logs/), but a summary here: * Since I have volumes already, I [instruct Rails to broadcast logs there](https://github.com/rubys/showcase/blob/aae08a6d57f92335b2cdbb94756e5416b7b50f83/config/environments/production.rb#L102-L110) in addition to stdout. The API to do this changed in Rails 7.1, and the code I linked to does this both ways. This data isn't replicated to other volumes and only contains Rails logs, as it really is only intended for when all else fails. * I have a [logger](https://github.com/rubys/showcase/tree/main/fly/applications/logger) app that I run in two regions. This writes log entries to volumes, and runs a small web server that I can use to browse and navigate the log entries. This data too is backed up (forever) to my home server, and is only kept for seven days on the log server. Two major additions since I wrote up that blog entry: * I have each machine [create a heartbeat log entry every 30 minutes](https://github.com/rubys/showcase/blob/main/bin/log-heartbeat.sh), and each log server runs a [monitor](https://github.com/rubys/showcase/blob/main/fly/applications/logger/monitor.ts) once an hour to ensure that every machine has checked in. If a machine doesn't check in, a [Sentry](https://sentry.io/) alert is generated. * I have my log tracker query Sentry whenever I visit the web page to see if there are new events. This serves as an additional reminder to go check for errors. I strongly encourage people to look into [Sentry](https://sentry.io/). ## Applying updates > [!NOTE] > > While Fly.io will start machines fast, you have to do your part. > If your app doesn't start fast, start something else that starts quickly first > so your users see something while they are waiting. If you go to [https://smooth.fly.dev/](https://smooth.fly.dev/) the server nearest to you will need to spin up the index application. And if, from there, you click on demo, a second application will be started. While you might get lucky and somebody else (or a bot) may have already started these for you, the normal case is that you will have to wait. Since all the services that are needed to support this application are on one machine, `fly deploy` can't simply start a new machine, wait for it to start, direct traffic to it, and only then start dismantling the previous machine. Instead, each machine needs to be taken offline, have the rootfs replaced with a new image, and all of its services restarted. Once that is complete, migrations need to be run on that machine; and in my case I may have several SQLite databases that need to be upgraded. I also have each machine that comes up rsync with the primary region. And finally, I generate an operational NGINX configuration and run [nginx reload](https://docs.nginx.com/nginx/admin-guide/basic-functionality/runtime-control/). While this overall process may take some time, NGINX starts fast so I start it first with a [configuration](https://github.com/rubys/showcase/blob/main/config/nginx.startup) that directs everything to a [Installing updates..](http://smooth.fly.dev/showcase/503.html) page that auto refreshes every 15 seconds which normally is ample time for all this to occur. Worst case, this page simply refreshes again until the server is ready. Even with this in place, it still is worthwhile to make the application boot fast. Measuring the times of the various components of startup, in my case Rails startup time dominates, and it turns out that effectively `bin/rails db:prepare` starts Rails once for each database. I found that I can avoid that by [getting a list of migrations](https://github.com/rubys/showcase/blob/cb3a05fd48c22a140effcf03ccfa9bd038a0df30/config/tenant/nginx-config.rb#L186), and then [comparing that list to the list of migrations already deployed](https://github.com/rubys/showcase/blob/cb3a05fd48c22a140effcf03ccfa9bd038a0df30/config/tenant/nginx-config.rb#L207-L215). I also moved preparation of my demo database to [build time](https://github.com/rubys/showcase/blob/cb3a05fd48c22a140effcf03ccfa9bd038a0df30/Dockerfile#L139-L140). In most cases, this means that my users see no down time at all, just an approximately two second delay on their next request as Rails restarts. ## Administration > [!NOTE] > > While Fly.io provides a [Machines API](/machines/api), > you can go a long way with old-fashioned scripting using the [flyctl](/flyctl) > command. Now lets look at what happens when I create a new event, in a new region. I (a human) get an email, I bring up my admin UI (on an instance of the index app I mentioned above). I fill out a form for the new event. I go to a separate page and add a new region. I then go to a third form where I can review and apply the changes. This involves regenerating my map, and I need to update the NGINX configuration in every existing region. There are shortcuts I can utilize which will update files on each machine and reload the NGINX configuration, but I went that way I would need to ensure that the end result exactly matches the reproducible result that a subsequent `fly deploy` would produce. I don't need that headache, so I'm actually using `fly deploy` which can add tens of seconds to the actual deploy time. I started out running the various commands manually, but that grew old fast. So eventually I invested in creating an admin UI. Here's what the front page of the admin UI looks like: ![Showcase Administration](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/shared-nothing/assets/adminui-cover.webp?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=adb16fa59e97d33b9aa1c7c1d2dbc24e) From there, I can add, remove, and reconfigure my events, and apply changes. I can also visit this app on three different servers, and see the logs from each. Finally, I have links to handy tools. The Sentry link that is current gray turns red when there are new events. I'm actually very happy with the result. The fact that I could get an email and add an event and have everything up and running on a server half a world away in less than five minutes is frankly amazing to me. And to be honest, most of that is waiting on the human (me), followed by the amount of time it takes to build and deploy a new image in nine regions. And in most cases, I can now make and deploy changes from my smartphone rather than requiring my laptop. For those who are curious, here's the [deployment script](https://github.com/rubys/showcase/blob/main/bin/apply-changes.rb). ## Costs > [!NOTE] > > This is a decent sized personal project, deployed across three continents. Even with a > significant amount of over-provisioning, the costs per month are around \$75, before taking > into account plans and allowances. I'm currently running this app in nine regions. I have two machines running a log server. I have two machines that will run puppeteer and Chrome on demand. [Fly.io pricing](/about/pricing) includes a number of plans which include a number of allowances. To keep things simple, I'm going to ignore all of that and just look at the total costs. From there, add your plan and subtract your allowances to see what this would cost for you. And be aware that things may change, the below numbers are current as of October 2026. Let's start with compute. 9 shared-cpu-1x machines with 1GB each at \$6.70 per month total \$60.30. Two shared-cpu-1x machines with 512MB each at \$3.69 per month total \$7.38. The print on demand machines are on bigger machines, but are only run when requested. I've configured them to stay up for 15 minutes when summoned. Despite all of this the costs for a typical month are around \$0.03. So we are up to \$67.71. Next up volumes. I have 9 machines with 3GB disks, and 2 machines that only need 1G each. 29GB at \$0.15 per month is \$4.35. I have a dedicated IPv4 address to use ssh. This adds \$2.00 per month. Outbound transfer is well under the free allowance, but for completeness I'm seeing about 16GB in North America and Europe, which at \$0.02 per GB amounts to \$0.32. And about 0.1GB in Asia Pacific, which at \$0.04 per GB adds less than a cent, but to be generous, let's accept a total of \$0.33. Adding up everything, this would be 67.71 + 4.35 + 2.00 + 0.33 for a total of \$74.39 per month. Again, this is before factoring in plans and allowances, and reflects the current pricing in October 2026. For my needs to date, I'm clearly over-provisioning, but it is nice to have a picture of what a fully fleshed out system would cost per month to operate. ## Summary > [!NOTE] > > Shared Nothing architectures are a great way to scale up applications where the data involved is easily partitionable. Key points (none of these should be a surprise, they were highlighted in the text above): * Run as many services as you like on a single machine; this both increases throughput and reliability. * You can even run multiple instances of the same app on a machine. * Distributing your apps across multiple regions lets you service requests close to your users. * For larger tasks, start up machines on demand. * Every backup plan should have a backup plan. * Not only should you have logs, logs should be monitored and have redundancy. * Make sure that you start something quickly -- even if it is only a "please wait" placeholder -- to make sure that your app is always servicing requests. * Create an administration UI and write scripts you can use to change your configuration. And finally, this app currently supports 77 events in 36 cities with 9 servers for less than you probably pay for a dinner out a month or a smart phone plan. ## Background > [!NOTE] > > Feel free to skip or skim this section. It describes how the application came to be, what it does, and a high level description of its usage needs. Background information on how this application came to be, and got me involved with Fly.io can be found at [Unretiring](http://intertwingly.net/blog/2022/08/13/Unretiring) on my personal blog. Here's an excerpt from that post: > I was at a local ballroom dance competition and found myself listed twice in one heat - a scheduling mishap where I was expected to be on the floor twice with two different dance partners. > > After the event, Carolyn and I discovered that the organizers used a spreadsheet to collect entries and schedule heats. A very manual and labor prone process, made more difficult by the need to react to last minute cancellations due to the ongoing pandemic. > > Carolyn told the organizers that I could do better, and volunteered me to write a program. I thought it would be fun so I agreed. This application is [on GitHub](https://github.com/rubys/showcase#readme), and while each individual event requires authentication for access, a full function demo can be accessed at [https://smooth.fly.dev/](https://smooth.fly.dev/). It was originally deployed a single Mac mini, and now is running on a small fleet of machines in the Fly.io network. *** Couples ballroom dance competitions are called a "Showcase". The application supports: * managing all the contestants * storing and playing dance music for solos * invoicing students and studios * managing the schedule * printing heat sheets, back numbers, and labels * recording the judges' scores * publishing results As you might expect, a Showcase competition happens live and in one location. There are very few users who need access to the system and they often are all physically in the same location. Here we'll cover how a Ruby on Rails application using a SQLite database can be deployed to the nearest Fly.io region for the event and how elegantly it solved this problem, including the ability to easily deploy new servers from the application. *** The core Showcase application is implemented on top of Ruby on Rails, augmented by two separate services implemented in JavaScript an run using [Bun](https://bun.sh/). While the details may vary, many of the core principles described below apply to other applications, even applications using other frameworks. In fact, many of the pieces of advice provided below apply to apps you host yourself, or host on other cloud providers. But first, some useful approximations that characterize this app, many of which will guide the choice of architecture: * Each event is a separate instance of the Rails application, with a its own separate database. * A typical database is one megabyte. Not a typo: one megabyte. Essentially, the app is a spreadsheet with a [Turbo](https://turbo.hotwired.dev/handbook/introduction) web front end. * All the services needed to support an event are run on a single VM. * Persistence consists of the database, storage (used for uploaded audio files), and logs. * Typical response time for a request is 100ms or less. * Peak transaction per second per database is approximately 1. Again, not a typo -- think spreadsheet. * Peak number of simultaneous users is often one, but even when there are multiple they tend to be all in the same geographic region, often in the same room (example: multiple judges entering scores). * 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](/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)](/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](/networking) The comprehensive networking overview (private networks, Anycast, WireGuard tunnels, routing) — helpful when building globally distributed isolated nodes in a shared‐nothing architecture. # Separate staging and production environments Source: https://docs.fly.io/blueprints/staging-prod-isolation Isolate production from staging on Fly.io by giving each environment its own organization, with separate members, apps, and fly.toml files. ![Illustration by Annie Ruygt of a gateway and buildings](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/staging-prod-iso.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=ecc0f5d6db0aeda4d6718ba0f9a446d2) ## 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](/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. Before you create a new organization, here's what you need to know: * You can consolidate billing for multiple organizations under a single parent organization, or you can create regular organizations with separate payment methods and billing. Learn more about [unified billing](/about/billing#unified-billing). * You invite or remove members in each organization separately. * An organization can have multiple apps and those apps can communicate with each other securely over the org's private network. Adjust the pattern to fit your needs. For example: * personal organization: the org you started with, and the one you keep for personal projects * staging organization: `-staging` used for development and testing * production organization: `-production` used for your production app ## Work with multiple organizations It's best if you use one Fly.io account to manage all your organizations, so you can access them without logging in and out. App names are unique across the Fly.io platform, so your staging and production apps will have different names and their own `fly.toml` files for configuration. ### Manage organizations in your dashboard To create a new org from the [dashboard](https://fly.io/dashboard/), select **Create new organization** from the **Organization** dropdown. To view or send invites to members, use the **Organization** dropdown to choose an org, then go to **Team**. ### Manage organizations with flyctl To create new organizations and invite or remove members, use [`fly orgs` flyctl commands](/flyctl/cmd/fly_orgs). When you have more than one org, flyctl prompts you to choose an organization when required. You can run commands on a specific app in any org using the `--app` option if you aren't in the app's project source directory in your terminal. ## Other kinds of isolation and access control * When you only need app isolation within an org, you can use [custom private networks](/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](/apps/going-to-production) A broad checklist for production‑readiness that includes isolating environments and access control. * [Staging environments with GitHub actions](/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. # Sticky sessions (session affinity) Source: https://docs.fly.io/blueprints/sticky-sessions Send a user's requests to the same Fly Machine with the fly-force-instance-id request header from the client or the fly-replay response header from your server. ![Illustration by Annie Ruygt of a machine sticking envelopes together](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/session-affinity.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=0e5163053121defb1f8cfb36e6ff8bed) ## Overview 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 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`. An example `` tag: ```erb theme={null} ``` These attributes identify the Machine instance to direct future requests to, and the name of the Stimulus controller to be used to make this happen. Place the following into `app/javascript/controllers/sticky-session.js`: ```js theme={null} import { Controller } from "@hotwired/stimulus" // Connects to data-controller="sticky-session" export default class extends Controller { connect() { document.documentElement.addEventListener( 'turbo:before-fetch-request', this.beforeFetchRequest ) } disconnect() { document.documentElement.removeEventListener( 'turbo:before-fetch-request', this.beforeFetchRequest ) } beforeFetchRequest = event => { event.detail.fetchOptions.headers['fly-force-instance-id'] = this.element.dataset.instance; } } ``` This code will subscribe and unsubscribe from [turbo:before-fetch-request](https://turbo.hotwired.dev/reference/events#turbo%3Abefore-fetch-request) events and insert a `fly-force-instance-id` header into requests. ## Fly-Replay 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](/networking/dynamic-request-routing#requirements-and-limitations). The example below creates [Express Middleware](https://expressjs.com/en/guide/using-middleware.html) that routes requests based on a session cookie: ```js theme={null} // Import required modules import express from "express"; import cookieParser from "cookie-parser"; // Create an Express app const app = express(); // Middleware to parse cookies app.use(cookieParser()); app.use((request, response, next) => { // 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(); 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 { // Route to the correct machine response.set('Fly-Replay', `instance=${targetMachineId}`); response.status(307); response.send(); } }); // Start the Express server const port = 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); }); ``` ### 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 theme={null} [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. 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. For complete details on replay caching configuration, see [Session-based Replay Caching](/networking/dynamic-request-routing#session-based-replay-caching). ## Related reading * [Dynamic Request Routing with `fly‑replay`](/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](/reference/load-balancing) Details how the Fly Proxy routes traffic among Machines, which underpins how session‑affinity decisions get made. * [Networking](/networking) A broad overview of how Fly handles public/private networking, Anycast IPs, WireGuard mesh, domain routing, and more. # Crontab with Supercronic Source: https://docs.fly.io/blueprints/supercronic Run cron jobs inside your app container on Fly.io with Supercronic: add a crontab, install it in your Dockerfile, and scale a single cron process group. ![Illustration by Annie Ruygt of Framkie the balloon wearing a sash and a prize ribbon in a gymnasium hall while a bird looks with admiration](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/supercronic.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=bb6dd95b9ca8f6a21ac150074882a5bd) ## 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. ## Create a crontab file In the root of your project, add a `crontab` file. ``` touch ./crontab ``` If you need to run a job every 5 minutes, your `crontab` file would something like this: ``` */5 * * * * echo "hello world!" ``` Check out [cron.help](https://cron.help) if you need a quick crontab syntax reference. ## Install `supercronic` in the container The [latest releases for supercronic](https://github.com/aptible/supercronic/releases) are on [GitHub](https://github.com/aptible/supercronic), where they include copy pasta 🍝instructions for getting it in your Dockerfile. The example below uses v0.2.29. Check the [releases page](https://github.com/aptible/supercronic/releases) for the latest version and its checksum, and update `SUPERCRONIC_URL` and `SUPERCRONIC_SHA1SUM` to match: ``` # Latest releases available at https://github.com/aptible/supercronic/releases ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \ SUPERCRONIC=supercronic-linux-amd64 \ SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b RUN curl -fsSLO "$SUPERCRONIC_URL" \ && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ && chmod +x "$SUPERCRONIC" \ && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \ && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic # You might need to change this depending on where your crontab is located COPY crontab crontab ``` After you put that in your Dockerfile, we're going to configure Fly to run only one instance of `supercronic`. Why? Because if you have a cronjob that does something like deliver emails to customers, you only want them to get that once. The last thing you want is them getting as many emails from your services that matches the number of instances you're running. ## Setup a web & cron process At the bottom of the `fly.toml` file, add the following: ``` [processes] # The command below is used to launch a Rails server; be sure to # replace with the command you're using to launch your server. web = "bin/rails fly:server" cron = "supercronic /app/crontab" ``` Then we have to tell Fly that your `web` process matches up with a service by having this under the `[[services]].` ``` # Make sure there's only one `processes` entry under `[[services]]` [[services]] processes = ["web"] ``` This change tells Fly that your `web` processes that you defined under `[processes]` will be exposed to the public internet. Your `cron` process won't be exposed to the public Internet because it doesn't need to! ## Deploy & scale the new processes Now that we've added `supercronic` to our `Dockerfile`, put the `crontab` at the root of our project folder, and reconfigured the `fly.toml` file, we're ready to deploy to production. ``` $ fly deploy ``` Then we'll need to scale the processes so that we only run one virtual machine container with the `cron` process. Be sure to change `web` to whatever number you had before. ``` $ 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 * [https://github.com/fly-apps/supercronic](https://github.com/fly-apps/supercronic) * [https://cron.help](https://cron.help) ## Related Reading * [Task scheduling guide with Cron Manager and friends](/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](/launch/processes) Explains how to define processes in `fly.toml`, run multiple programs (web, cron, workers), scale by group. * [App Configuration (`fly.toml`)](/reference/configuration) In‑depth look at the config file that orchestrates your services, processes, health checks, etc. # Task scheduling guide with Cron Manager and friends Source: https://docs.fly.io/blueprints/task-scheduling Compare ways to run scheduled jobs on Fly.io, from Cron Manager's one-off Machines to Supercronic, scheduled Machines, and in-app schedulers. ![Illustration by Annie Ruygt of Frankie the hot air balloon scheduling some tasks on his calendar](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/task-scheuling.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=abd94df491dea493ce903ab415a178f5) ## 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) 2. Create a new Fly app and deploy it with `fly deploy` 3. Set your `FLY_API_TOKEN` as a secret for the app 4. Define your job schedules in `schedules.json` Here’s a sample job: ```json theme={null} { "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](/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 2. Install Supercronic in your Dockerfile 3. In `fly.toml`: ``` [processes] app = "bin/server" cron = "supercronic /app/crontab" ``` 1. Scale it: ```bash theme={null} 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](/blueprints/supercronic) * [Deferring long-running tasks to a work queue](/blueprints/work-queues) # Use Docker base images for faster deploys Source: https://docs.fly.io/blueprints/using-base-images-for-faster-deployments Build a shared base image as its own Fly.io app, push it to the Fly registry, and use it in FROM so each deploy rebuilds only your app, not every dependency. ![Illustration by Annie Ruygt of the Docker whale carrying some containers](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/using-base-images.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=4ae504b9cb02d279c9ecf76aeb7ecfe7) ## 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 theme={null} 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 theme={null} 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 theme={null} $ 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 theme={null} $ 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 theme={null} $ 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 theme={null} 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 theme={null} 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 theme={null} $ 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 theme={null} 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 theme={null} $ 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 theme={null} $ 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 theme={null} $ 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](/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](/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`)](/reference/configuration) Details on how your `fly.toml` orchestrates builds, images, and deployments. # Managing Docker Images with Fly.io's Private Registry Source: https://docs.fly.io/blueprints/using-the-fly-docker-registry Tag and push Docker images to registry.fly.io, deploy them with fly deploy --image, and reuse images across apps in your Fly.io organization without fly launch. ![Illustration by Annie Ruygt of Frankie the balloon and the Docker whale playing a game of bean bag toss](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/using-the-fly-docker-reg.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=041c21ca666f7be361d21a2661a447af) ## 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 theme={null} fly apps create ``` App names must be globally unique. You can generate a unique one automatically: ```sh theme={null} fly apps create --generate-name ``` Then tag your image for the app's registry: ```sh theme={null} docker build -t registry.fly.io/: . ``` ## Authenticating and pushing to the registry Authenticate Docker to use the Fly.io registry: ```sh theme={null} 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 theme={null} 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 theme={null} 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 theme={null} [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 theme={null} 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 theme={null} 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 theme={null} fly releases -a my-app-name --image ``` If you're using the GraphQL API to query releases, use the following query format: ```json theme={null} { "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](/blueprints/using-base-images-for-faster-deployments#how-to-make-a-base-image) blueprint. ## Related reading * [Deploy with a Dockerfile](/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`)](/reference/configuration) Details on how your `fly.toml` orchestrates builds, images, and deployments. # Using Fly Volume forks for faster startup times Source: https://docs.fly.io/blueprints/volume-forking Preload ML models, SQLite databases, or other large files onto a Fly Volume once, then fork it so new Machines boot with the data already in place. ![Illustration by Annie Ruygt of a blimp and a Fly rocket flying side by side](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/volume-forking.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=9b5c32345d8405ad337af6aab1a9fc87) > [!NOTE] > > **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](/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](/volumes/overview) * [Create and manage volumes](/volumes/volume-manage) * [Volume states](/volumes/volume-states) * `fly volumes fork` [reference](/flyctl/cmd/fly_volumes_fork) # Warm pools of user Machines Source: https://docs.fly.io/blueprints/warm-pool-user-machines Make per-user apps start instantly: keep a pool of pre-provisioned Fly apps, volumes, and booted Machines, then claim one and configure it when a user signs up. ![Illustration of five smiling cube-shaped Machines relaxing together in a steaming hot tub](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/warm-pool-user-machines.webp?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=0b1a31241091b7b6e56045c33968d5a9) This page covers how to make per-user infrastructure feel instant: pre-provision a pool of generic Fly Machines in the background, then claim one and personalize it when a user shows up. It's the "pre-create a pool of these" advice from [Per-User Dev Environments](/blueprints/per-user-dev-environments), worked out in full: the database schema, the API calls, the claim query, and the maintenance loop. ## The problem If you run per-user apps (dev environments, AI agents, code sandboxes), onboarding a user means creating a Fly app, a [volume](/volumes), and a Machine, then waiting for the software inside to boot. Each step is fast, but they add up: a minute or more before the user has something to talk to. Users expect seconds. Most of that work is generic. The app, the volume, the Machine, the boot: none of it depends on who the user is. Only the last step, configuration, is user-specific. So do the generic work ahead of time. A background worker keeps N instances provisioned, booted, and health-checked. When a user signs up, you claim one from the pool, write their config into it, and hand it over. Allocation drops to about a second. If the pool is empty, you fall back to provisioning directly. That's slower, but nothing breaks. ## One app per pool entry Each pool entry is a dedicated Fly app, not just a Machine in a shared app. [The app is your isolation boundary](/machines/guides-examples/one-app-per-user-why): creating it with its own network name puts each entry on its own [private 6PN network](/networking/private-networking), so one user's Machine can never reach another's. A [Flycast address](/networking/flycast) gives your control plane private routing to the Machine without a public IP. And deleting the app cascades to its Machines and volumes, so teardown is a single API call, which matters more than it sounds, because crash-recovery cleanup uses the same call. This control plane creates and destroys apps continuously, so it needs an org-scoped API token (`fly tokens create org`), not an app-scoped deploy token. Keep it server-side and never expose it to a user Machine. ```bash theme={null} # Create the app on its own private network curl -X POST "https://api.machines.dev/v1/apps" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "app_name": "pool-x7f3k2", "org_slug": "my-org", "network": "pool-x7f3k2" }' # Allocate a Flycast (private) address into the app's own network curl -X POST "https://api.machines.dev/v1/apps/pool-x7f3k2/ip_assignments" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"type": "private_v6", "network": "pool-x7f3k2"}' ``` Pass the same `network` name you created the app with. The `network` field on the allocation targets which 6PN network the Flycast address routes within, and it does not default to the app's own network. Leave it empty and the address lands in your organization's default network, where it cannot reach a Machine that lives in the app's isolated per-app network. App creation returns `422` if the name is taken. Your worker will retry after crashes, so treat that as idempotent: `GET /v1/apps/{name}` to confirm the app exists and reuse it. If it doesn't exist, the `422` was a real validation error, so rethrow it. ## Track pool state in a database The Machines API is your source of truth for infrastructure. A small table is your source of truth for who owns what. One row per pool entry: | column | purpose | | - | - | | `id` | your identifier | | `app_name` | the entry's dedicated Fly app | | `machine_id` | written as soon as the Machine is created | | `volume_id` | written as soon as the volume is created | | `region` | the concrete region Fly actually placed it in | | `status` | `provisioning` → `ready` → `failed` | | `provisioned_at` | set when the entry becomes `ready` | | `allocated_at` | `NULL` = available; set = claimed | | `allocated_to` | the user or instance that claimed it | | `provision_version` | bump to invalidate the pool when your image changes | Two rules make this crash-safe. First, insert the row *before* touching the Machines API, so nothing exists on Fly.io that your database can't see. Second, write resource IDs back incrementally: the volume ID right after volume creation, the Machine ID right after Machine creation. If the worker dies mid-provision, a sweeper can find the half-finished row, delete the app (cascading whatever got created), and move on. ## Provision in the background The sequence for each entry: create app → allocate Flycast IP → create volume → create Machine → wait for boot → health-check → mark `ready`. ```bash theme={null} # Create the volume first; Fly resolves the region curl -X POST "https://api.machines.dev/v1/apps/pool-x7f3k2/volumes" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"name": "user_data", "region": "iad", "size_gb": 1}' # Create the Machine in the *volume's* region curl -X POST "https://api.machines.dev/v1/apps/pool-x7f3k2/machines" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "name": "pool", "region": "iad", "config": { "image": "registry.fly.io/my-user-app@sha256:...", "guest": {"cpu_kind": "shared", "cpus": 1, "memory_mb": 2048}, "init": {"swap_size_mb": 2048}, "mounts": [{"volume": "vol_abc123", "path": "/data"}], "services": [{ "internal_port": 8080, "ports": [{"port": 80, "handlers": ["http"]}], "autostart": true, "autostop": "off" }], "checks": { "health": {"type": "tcp", "port": 8080, "interval": "15s", "timeout": "5s", "grace_period": "120s"} }, "restart": {"policy": "on-failure", "max_retries": 3}, "metadata": {"pool_entry": "fp_abc123"} } }' # Wait for the VM to boot (timeout defaults to 60s, which is also the max) curl "https://api.machines.dev/v1/apps/pool-x7f3k2/machines/${MACHINE_ID}/wait?state=started&timeout=60" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" ``` A few settings in that config matter more than they look. **Volume and Machine must co-locate.** A volume is pinned to one physical host, and the Machine that mounts it has to land on that same host. Create the volume first, then create the Machine with that volume ID in `config.mounts` and use the volume's returned region. The mount is what places the Machine on the volume's host; matching the region alone isn't enough, and creating the Machine first can fail to attach even in the right region. Store the Machine's returned region in your database too, since that's where it actually landed. **Pin the image by digest, not a tag.** `provision_version` is meant to mark a uniform pool generation. If you build on a moving tag like `:latest`, two entries with the same version can end up running different images. Reference an immutable digest (`@sha256:...`) and bump `provision_version` whenever it changes. **`autostop: "off"` is the point.** Pool Machines are idle by design. [Autostop](/launch/autostop-autostart) would helpfully shut them down, and a stopped pool Machine is a cold start with extra steps. **`started` doesn't mean ready.** The `wait` endpoint tells you the VM booted, not that your app inside is serving. Poll your app's health endpoint through the [exec endpoint](/api/machines/machines/execute-command) before marking the entry `ready`: ```bash theme={null} curl -X POST "https://api.machines.dev/v1/apps/pool-x7f3k2/machines/${MACHINE_ID}/exec" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"command": ["curl", "--fail", "--silent", "--max-time", "5", "http://localhost:8080/healthz"]}' ``` The exec call returns HTTP 200 even when the command inside failed, so check the response body's `exit_code`, not the API status. `--fail` makes the inner `curl` return non-zero on a 5xx, so a wedged app reads as unhealthy instead of ready. (Whatever probe you use has to exist in the image.) This is what makes claiming instant later. A `ready` entry is one whose app is already serving, so the only work left at claim time is configuration. Skip this and your "instant" claims hand users a Machine that's still booting. Pool Machines don't know their user yet, so boot them with a minimal placeholder config (and dummy values for any env vars your entrypoint insists on). The real config arrives at claim time. If any provisioning step fails, delete the app (it cascades) and mark the row `failed` for the sweeper. ## Claim atomically with SKIP LOCKED When a user signs up, claim an entry with one atomic statement: ```sql theme={null} UPDATE pool SET allocated_at = NOW(), allocated_to = $user_id WHERE id = ( SELECT id FROM pool WHERE status = 'ready' AND allocated_at IS NULL ORDER BY provisioned_at ASC -- oldest first FOR UPDATE SKIP LOCKED LIMIT 1 ) RETURNING * ``` `FOR UPDATE SKIP LOCKED` is doing the heavy lifting: concurrent signups never grab the same entry and never wait on each other's locks. No advisory locks, no queue, no coordinator. Prefer the user's [region](/reference/regions), but don't insist on it. Run the query filtered to nearby regions first, then unfiltered. A warm Machine in the wrong region beats a cold start in the right one. If the query returns zero rows, provision directly using the same sequence as the background worker; the pool is an optimization, never a dependency. Be careful how you unwind a failed claim. If the claim fails *before* you write anything user-specific, clearing `allocated_at` safely returns the entry to the pool. Once you've written the user's config or secrets to the volume, that entry is contaminated: returning it to the pool can hand the next user the previous one's data. After any user-specific write, mark the entry `failed` and let the maintenance loop destroy it. Don't recycle it. ## Personalize with exec, not a Machine update There are two ways to turn a generic pool Machine into *this user's* Machine, and the difference between them is most of the payoff: | approach | mechanism | latency | | - | - | - | | exec write | write the user's config to the volume via exec; the app hot-reloads it | \~1-2s | | Machine update | `POST /machines/{id}` with new config, full Machine restart | \~20s | If your app can watch a config file and reload it, write the file and you're done. Base64-encode the user's config, drop it in where the exec command decodes it, and write to a temp file before renaming so a reader never sees a half-written config: ```bash theme={null} # CONFIG_B64 = base64 of the user's config; splice it into the command below curl -X POST "https://api.machines.dev/v1/apps/pool-x7f3k2/machines/${MACHINE_ID}/exec" \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"command": ["sh", "-c", "umask 077; echo CONFIG_B64 | base64 -d > /data/config.json.tmp && mv /data/config.json.tmp /data/config.json"]}' ``` This also decides where per-user secrets should live: in the config file on the volume, not in Machine env vars. Env changes require a Machine update and a restart. A file write doesn't. When you do need a real Machine update (rename, metadata, env vars), use the [lease flow](/machines/api/machines-resource#create-a-machine-lease) so concurrent updaters can't clobber each other: acquire a lease, send the update with the `fly-machine-lease-nonce` header, release the lease in a `finally`. And know that Machine updates replace the entire config; they don't merge. `GET` the current Machine first and send back its full config with only your fields changed, or you'll silently wipe mounts, services, checks, and env. ## Maintain the pool A small worker, ideally its own [process group](/launch/processes), loops on an interval. Each tick does four things, in order: 1. **Cycle.** Delete unallocated entries whose `provision_version` doesn't match the current one. Bump the version whenever your image or Machine config changes, and the pool replaces itself over the next few ticks. 2. **Clean.** For `failed` entries, delete the app and the row. 3. **Validate.** `GET /machines/{id}` for each `ready` entry; discard anything whose `state` isn't `started`. Also discard rows stuck in `provisioning` for more than \~10 minutes. That's a crashed provisioner, and the incrementally-written IDs let you clean up whatever it left behind. 4. **Replenish.** Count available entries (`status = 'ready' AND allocated_at IS NULL`) plus in-flight `provisioning` entries against your target, and create the shortfall. Counting claimed-but-still-`ready` rows here is the classic bug: the pool looks full and quietly stops refilling as users claim entries. Spread the new entries across regions by weight (say, 3 in `us`, 1 in `eu`, 1 in `apac`, where those are your own buckets of concrete Fly regions like `iad`, `ams`, and `sin`). Counting in-flight `provisioning` entries stops you from over-provisioning during a burst of claims. Guard against overlapping runs. ## Pointers and footguns * **Warm Machines cost money.** Pool size is a bet on your peak claim rate versus your tolerance for cold-start fallbacks. Start small (2-5), log ready/provisioning counts every tick, and grow only when you see fallbacks under real traffic. If your app boots fast, a pool of *stopped* Machines that you `start` on claim may be the better trade, since stopped Machines cost much less than running ones. * **Mint unique secrets per entry** (auth tokens, gateway passwords) at provision time and store them encrypted in your database. A claimed Machine should never be reachable with another entry's credentials. * **Record what the API returned, not what you asked for.** Region inputs resolve at creation time; the Machine's actual region is what your claim query needs to match against. * **Machines are tied to physical hardware.** A host failure destroys the Machine and its volume. For pool entries that's fine, since the maintenance loop replaces them, but anything durable a user creates after claiming should also be backed up off-Machine, to somewhere like [Tigris](/tigris) or S3. * **TCP checks beat HTTP checks for pooled apps.** Your app's HTTP endpoints may require auth or per-user config that doesn't exist pre-claim. A TCP check on the listening port with a generous `grace_period` is the reliable signal. ## Related reading * [Per-User Dev Environments with Fly Machines](/blueprints/per-user-dev-environments): The architecture this pattern plugs into, including routing with `fly-replay`. * [Connecting to User Machines](/blueprints/connecting-to-user-machines): How to get traffic to a fleet of per-user Machines once you've assigned them. * [Why one app per user?](/machines/guides-examples/one-app-per-user-why): The isolation reasoning behind app-per-entry. * [Machines API reference](/machines/api): Apps, Machines, volumes, leases, and exec. # Background job queues with Celery, BullMQ, and Fly Machines Source: https://docs.fly.io/blueprints/work-queues Move slow work like PDFs, emails, and API calls out of web requests into a work queue, using Celery for Python, BullMQ for Node, or on-demand Fly Machines. ![Illustration by Annie Ruygt of a robot setting up a queue of boxes and tasks](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/work-queue.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=96eff74c3926353a7e322f9e534ada93) ## 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](/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). 2. A worker process picks up the task from the queue and executes it. 3. Task results can optionally be stored (e.g., in Redis or your database). ### Setup Install the basics: ```bash theme={null} pip install "celery[redis]" django-celery-results ``` Create a `celery.py`: ```python theme={null} 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 theme={null} 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 theme={null} 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 theme={null} fly deploy fly scale count worker=2 ``` > [!NOTE] > > **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. 2. It calls the Fly Machines API to spin up a temporary worker. 3. The worker reads job data from Redis, runs it, and writes back the result. 4. Your app polls Redis to check status and fetch results. 5. 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 theme={null} 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/) # Working with Docker on Fly.io Source: https://docs.fly.io/blueprints/working-with-docker How Fly.io turns Docker images into VMs: no Docker daemon, ephemeral filesystems, prebuilt images for faster deploys, image tags, and multi-container Machines. ![Illustration by Annie Ruygt of a Frankie the hot air balloon giving flowers to the Docker whale](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Docker-With-Fly.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=86e301947ed870df9811c5ee1842d1c0) > [!NOTE] > > **Fly.io runs apps close to your users by turning Docker images into VMs running in our global network. This is a practical guide for using Docker effectively on Fly.io.** ## Overview If you've deployed an app with `fly launch`, you've already worked with Docker on Fly.io. Behind the scenes, Fly.io builds every deployed app as a Docker image. But here's the twist: Fly.io doesn't run Docker containers. It uses Docker images as a packaging format. Once deployed, your app runs inside a lightweight VM booted from an unpacked image. This post explains what that means, how to work with images and tags, and how to speed up deployments by prebuilding images. Fly now also supports multi-container Machines. If you're looking for more information and use cases, check out [this forum post](https://community.fly.io/t/using-containers-with-flyctl/24729) and [this guide](/machines/guides-examples/multi-container-machines). ## Why you should care Understanding how Docker images work on Fly.io unlocks faster deployments, better image reuse, and more control over your app lifecycle. If you're running large builds, deploying the same app to multiple Fly apps, or trying to debug tricky release issues, Docker image fluency pays off. ## How Docker images work on Fly.io When you deploy to Fly.io, we take your Docker image and unpack it into a root filesystem for a virtual machine. That’s it. No Docker daemon. No containers. Just your app running inside a lightweight VM. At boot, each Fly.io machine runs a minimal init process that sets up the environment and kicks off whatever you’ve configured in your Dockerfile’s `ENTRYPOINT` and `CMD`. When your app exits, the VM shuts down. The filesystem is ephemeral. If your app writes files, installs packages, or modifies anything at runtime, those changes vanish when the machine stops. On the next boot, it’s back to the clean image. Want to keep data around? Mount a [volume](/volumes/overview). ## Prebuilding for faster deploys By default, `fly deploy` builds and deploys in one step. That can be slow if your Dockerfile does a lot of work. If you're deploying often, it can help to split build from release: ```bash theme={null} fly deploy --build-only --push ``` This builds your image and pushes it to the registry, but doesn't deploy it. When you're ready, deploy that prebuilt image with: ```bash theme={null} fly deploy --image registry.fly.io/myapp:your-tag-here ``` This shaves valuable seconds (or minutes) off deployment time, and lets you treat builds and releases as separate steps. ## Managing image tags When you run `fly deploy`, we build a Docker image and push it to Fly.io's private registry. That image gets a tag like `deployment-20231208-123456`. To deploy that same image again, or to another app in your org, you can reference it explicitly: ```bash theme={null} fly deploy --image registry.fly.io/myapp:deployment-20231208-123456 ``` Want friendlier tags? Use the `--image-label` flag: ```bash theme={null} fly deploy --image-label my-cool-feature-abc123 ``` You can then re-deploy it later with `--image` as shown above. This is especially handy for canary deploys or promoting the same image to staging and production. Want to manage images manually? Authenticate your Docker client with: ```bash theme={null} fly auth docker ``` This lets you tag, push, and pull images using standard Docker commands. ## Troubleshooting common Docker image issues * **My app is writing data, but it’s gone after restart!**\ That’s expected. Fly.io Machines reset their root filesystem each time they start. Use [volumes](/volumes/overview) for persistence. * **Why is my deploy slow?**\ Split build and deploy steps: run `fly deploy --build-only --push`, then `fly deploy --image` to skip rebuilding every time. * **I’m getting weird build errors.**\ Use `fly deploy --build-local` to build with your local Docker daemon. It's often easier to debug than remote builds. Or just use plain old Docker: `docker build -t myapp .` to build locally and `docker run` to test the image before deploying. * **My image builds fine but fails at runtime.**\ Check your `ENTRYPOINT` and `CMD`. Your app runs as PID 1 in a VM, not in a Docker container. Use `fly logs` to investigate. You can also `docker run` the image locally to spot runtime issues before deploying. * **My image isn’t deploying correctly to another app.**\ Make sure both apps are in the same Fly.io org, and reference the full tag from the Fly registry. * **My app crashes or misbehaves on startup. What’s going on?**\ Fly.io doesn’t run readiness checks before starting your app by default. If your app depends on something like a database or external service, you need to make sure it waits until that service is ready before it starts doing work. Add retries or startup logic to handle this gracefully. Logs are your friends - run `fly logs` on a separate console to see what the app does or says at startup. * **It works locally, but not in production.**\ That probably means “it runs on my machine,” not “the image I’m deploying runs correctly.” Build the image locally with `docker build` and test it with `docker run`. That’s usually the fastest way to spot missing files, bad assumptions, or environment mismatches. Also, pin your base images, audit your `.dockerignore`, and make sure everything your app needs is actually included in the image. ## Tips for optimizing your Dockerfile A few well-placed changes to your Dockerfile can dramatically improve build speed, runtime performance, and security: * **Use multi-stage builds.** Compile code and install build-only dependencies in one stage, then copy only what's needed into a smaller final image. * **Choose minimal base images.** Use Alpine distroless, or slim variants of Debian/Ubuntu to reduce image size and attack surface. Slim images often provide a good middle ground between minimalism and compatibility. * **Leverage layer caching.** Put the least frequently changing steps (like system packages) early in the file. * **Combine `RUN` steps.** Use `&&` to chain commands and reduce image layers. * **Clean up after installs.** Remove package caches and temp files in the same `RUN` block. * **Use `.dockerignore`.** Keep your build context clean: exclude `node_modules`, `.git`, logs, etc. * **Pin image versions.** Don’t rely on `latest`; specify exact versions to keep builds consistent. * **Avoid hardcoded secrets.** For *runtime* [secrets](/apps/secrets), use `fly secrets`. It’s built for exactly this, and injects secrets securely into your app’s environment. For *build-time* secrets (like private package tokens), use [build secrets](/apps/build-secrets) to avoid leaking them into your final image. * **Run as non-root.** Use `USER` to limit container privileges. * **Scan for vulnerabilities.** Tools like Trivy, Docker Scout, and others help keep your images safe. You can also try `fly registry vulns -a ` for a quick CVE scan of your deployed image (just note that it’s still experimental). These tips and practices can make your images smaller, safer, and more predictable to deploy. ## Related reading * [**Deploy with a Dockerfile**](/languages-and-frameworks/dockerfile) Find out how `fly launch` and `fly deploy` work when you're starting from a Dockerfile. * [**Managing Docker images with the Fly.io registry**](/blueprints/using-the-fly-docker-registry) Understand how image storage, tagging, and reuse work in Fly.io’s private registry. * [**Using base images for faster deploys**](/blueprints/using-base-images-for-faster-deployments) Learn how shared base images can shave time off your CI/CD loop. * [**Optimizing your Dockerfile**](https://docs.docker.com/build-cloud/optimization/) Advice straight from Docker on structuring builds for speed and reusability. * [**Customizing your Dockerfile for Laravel**](/laravel/advanced-guides/customizing-dockerfile) A Laravel-specific deep dive, but useful even if you’re not using Laravel. # Databases and storage Source: https://docs.fly.io/database-storage-guides Pick a data option on Fly.io: Managed Postgres, Upstash for Redis, Fly Volumes for persistent NVMe disk, Tigris object storage, or an external provider. ![Illustration by Annie Ruygt of a tiger sleeping next to a storage device](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/database-storage.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=5855ad17983f97ea8b30072892902d50) ## Managed database service [Fly.io Managed Postgres](/postgres) 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. ## Key-value stores **[Upstash for Redis](/upstash/redis)** - [Redis](https://redis.io/) is an in-memory database commonly used for caching. A managed service by [Upstash](https://upstash.com/). *** ## Fly Volumes - Disk 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](/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](/volumes/overview) to find out if volumes are the best solution for your use case. ## Object storage service **[Tigris Global Object Storage](/tigris)** - [Tigris](https://www.tigrisdata.com/) is a globally distributed S3-compatible object storage service hosted on Fly.io infrastructure. *** ## Have a project with special database or storage requirements? 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. # Your Fly App Source: https://docs.fly.io/deep-dive/application The Fly.io deep dive demo runs on two Fly Machines with autostop and autostart. Scale them vertically or with fly scale count, edit fly.toml, and redeploy. ![Illustration by Annie Ruygt of an app cube flying near two Fly balloons](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/your_fly_app.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=a2b085144d5411ff02ff543a08308dfe) Your demo app is running by default on two [Fly Machines](/machines), our fast-launching VMs. Both Machines stop when not in use, and start automatically when a new request comes in. [Autostop/autostart](/launch/autostop-autostart) is entirely configurable. You can choose 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. You can also [horizontally scale](/launch/scale-count) the number of Machines. You can scale out to different regions with the `fly scale count` command too. If you have a co-worker on another continent, create a Machine there. Familiarize yourself with the [fly.toml](/reference/configuration) config file. Make a change there--or to any part of your app--and run `fly deploy` to update your app. Your Fly App takes advantage of [Anycast routing](/networking/services), a [load-balancing proxy](/reference/fly-proxy), and [private networking](/networking/private-networking). You can also create [DNS certificates for custom domains](/networking/custom-domain). # Django demo reference Source: https://docs.fly.io/deep-dive/django How the Django dictaphone demo fits together: Django Storages on Tigris, Channels on Redis, Postgres via DATABASE_URL, and Celery jobs that call Whisper. The Django source is on [GitHub](https://github.com/fly-apps/django-dictaphone/). `fly launch` will provide a [`Dockerfile`](https://docs.docker.com/reference/dockerfile/) and a [`fly.toml`](/reference/configuration) config file. When you make changes to your application, you can use [dockerfile-django](https://github.com/fly-apps/dockerfile-django?tab=readme-ov-file#dockerfile-generator-for-django-projects) to produce updated Dockerfiles. How the pieces are put together: * The original web-dictaphone app (minus the HTML) is in the [static](https://github.com/fly-apps/django-dictaphone/tree/main/static) directory, and contains icons, scripts, styles, and a web manifest; all served as static files. * [`clips/templates/clips/index.html`](https://github.com/fly-apps/django-dictaphone/blob/main/clips/templates/clips/index.html) contains the HTML template. * [Django Models and databases](https://docs.djangoproject.com/en/5.1/topics/db/) is [configured](https://github.com/fly-apps/django-dictaphone/blob/2a9ec4ceb728e657aeec76477d87ea1a8627523a/dictaphone/settings.py#L89-L102) to use Sqlite3 as the database in development mode, and PostgreSQL in production. Access to the database is through the `DATABASE_URL` secret. * [Django Storages](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html) is [configured](https://github.com/fly-apps/django-dictaphone/blob/2a9ec4ceb728e657aeec76477d87ea1a8627523a/dictaphone/settings.py#L104-L117) to write to the filesystem in development and Tigris in production. The following secrets are used to establish the connection: `AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT_URL_S3`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY`, and `BUCKET_NAME`. * [Django Channels](https://channels.readthedocs.io/en/latest/) is [configured](https://github.com/fly-apps/django-dictaphone/blob/2a9ec4ceb728e657aeec76477d87ea1a8627523a/dictaphone/settings.py#L119-L135) to use an in memory channel in development and Redis in production based using the `REDIS_URL` secret. Key points of logic: * [`static/scripts/app.js`](https://github.com/fly-apps/django-dictaphone/blob/main/static/scripts/app.js) has been modified to make server requests at various points using [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). * [`clips/views.py`](https://github.com/fly-apps/django-dictaphone/blob/main/clips/views.py) contains the logic to build responses to requests for the index page, and to GET, PUT, and DELETE audio clips. * The server side realtime implementation code is primarily in [Django Channels](https://channels.readthedocs.io/en/latest/), so all that is needed is a few lines in [`clips/models.py`](https://github.com/fly-apps/django-dictaphone/blob/b1bb70cc513bd4977dc74fd56dfff86b8f2d12ec/clips/models.py#L20-L25). The client side is provided by [`static/scripts/websocket.js`](https://github.com/fly-apps/django-dictaphone/blob/main/static/scripts/websocket.js). When update notifications are received, the index page is re-retrieved and the body of the DOM is replaced with new contents. * When the `WHISPER_URL` secret is set, `PUT` requests will cause the audio clips to be passed to the Whisper server, and responses will be used to update the PostgreSQL database. This is done using [Celery](https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html). The code for this is in [`clips/tasks.py`](https://github.com/fly-apps/django-dictaphone/blob/main/clips/tasks.py). # Deep dive demo Source: https://docs.fly.io/deep-dive/index An hour-long Fly.io demo deploying MDN's Web Dictaphone with Postgres, Tigris, and Upstash Redis WebSockets in Django, Next.js, Node, Phoenix, or Rails. ![Illustration by Annie Ruygt of a bird in a diver costume, swimming under water](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/deep-dive.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=308c9cfaf3fff62f23d01a7ccc79eae5) Welcome to our deep dive demo, where you can explore Fly.io more thoroughly, but in a time-boxed way. In one hour or less: get a fully-functioning app running in the first few minutes, and then have enough time left over to understand what you just did and explore how the pieces fit together. ## Goals of the deep dive It's really just one goal: **Help you find out if Fly.io is the right place for you.** If you came from our [Speedrun](https://fly.io/speedrun) or [Getting started](/getting-started) pages, maybe you want to go beyond `hello world` and explore the platform so you can feel confident that you're choosing a provider that can support you now, and scale with you later on as you grow. Maybe you're concerned about lock in. You want to use services from other places and even eject entirely and go elsewhere if things don't work out. Perhaps it is a simple as you feel it is easier to tune and evolve a working system than it is to debug why your application doesn’t launch on an unfamiliar platform. If you don't get a good feeling within an hour, you're out of here. If you do get a good feeling, then stick around and try launching the app that you brought here to launch. ## Beyond `hello fly` A real-world application has, at a minimum, the following components: * An HTML form and a database; typically a relational database. * The ability to handle media files or documents, generally using S3. * A multi-user and realtime component, where changes made by one person in one location are reflected instantly in the browser of another person. To get a fully-functional app running smoothly, you need a whole bunch of things; things like Anycast routing, load balancers, DNS certificates, WebSockets, an internal private network, a relational database, an object store, and an in-memory database. And the knowledge to connect them all together. Connecting complex components together generally takes an unpredictable amount of time due to surprises. Usually, set up for a complete app typically takes a *minimum* of an afternoon's worth of work, even if you're familiar with a cloud platform. We will be up and running in minutes. Fly.io minimizes surprises by taking a lot of work off your plate with our [Public Network Services](/networking/services), [Fly Proxy routing](/reference/fly-proxy), out-of-the-box [Private Networking](/networking/private-networking), and preconfigured databases. While we can't promise no surprises, we can show you how we'll partner with you to handle some of the dev ops complexities of working in a public cloud. ## So what is this deep dive demo app? The deep dive demo uses industry standard components that you can run on your laptop, a VPS, AWS EC2, Google Compute Engine, or Azure. ### Web Dictaphone -- Fly.io edition The deep dive demo is based on [MDN's Web Dictaphone](https://github.com/mdn/dom-examples/tree/main/media/web-dictaphone). You can play with a [live demo hosted on GitHub](https://mdn.github.io/dom-examples/media/web-dictaphone/). The Web Dictaphone app is about as basic of an HTML form as you can get, and it has the added bonus of providing the ability to generate as many media files as you want using only your voice. The basic Web Dictaphone is client side only, requiring a web server that can deploy static assets (HTML, CSS, JS, images), like nginx, Apache HTTPd, or Caddy. Storing the data in databases for our deep dive demo requires a server that can handle HTTP GET, POST, PUT, and DELETE requests. The choice of server varies depending on the language or framework. The demo includes the following components: * A [PostgreSQL](https://www.postgresql.org/) relational database to store the names of the audio clips * A [Tigris bucket](https://www.tigrisdata.com/) to store the audio files * [Upstash for Redis](/upstash/redis) and [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) to handle the connections for the realtime requirement ### Available runtimes Our deep dive demo comes in five flavors (so far): * [Django](/deep-dive/django) * [Next.js](/deep-dive/nextjs) * [Node.js](/deep-dive/nodejs) * [Phoenix](/deep-dive/phoenix) * [Rails](/deep-dive/rails) # Launch the demo Source: https://docs.fly.io/deep-dive/launch-deep-dive Install flyctl, then run fly launch --from with the Django, Node.js, Next.js, Phoenix, or Rails dictaphone repo to deploy the deep dive demo on Fly.io. Let's get right to it. **Step 0:** To get set up to run the deep dive demo on Fly.io, all you need to do is install Node, Phoenix, Python, or Ruby and create an empty project directory. After that there are only two steps. **Step 1:** [Install flyctl](/flyctl/install). **Step 2:** Run one of the following: ```bash theme={null} fly launch --from https://github.com/fly-apps/django-dictaphone ``` -or- ```bash theme={null} fly launch --from https://github.com/fly-apps/node-dictaphone ``` -or- ```bash theme={null} fly launch --from https://github.com/fly-apps/nextjs-dictaphone ``` -or- ```bash theme={null} fly launch --from https://github.com/fly-apps/phoenix-dictaphone ``` -or- ```bash theme={null} fly launch --from https://github.com/fly-apps/rails-dictaphone ``` If you're new to Fly.io, the second step will take you to a page where you can register before continuing. The `fly launch` output describes what you'll be getting for the app, and gives you an opportunity to tweak the settings. (Suggestion: don't. The defaults are fine for this demo and we'll walk you through how to adjust them later.) And then it will build and assemble and wire up your app. Take your time and play with it. Open the application in multiple browser windows. Send a link to a friend on another continent and watch your browser update in realtime. And then relax. We promised you it would be less than an hour. You're already up and running. If you are so inclined, try bringing up this exact same application on another cloud provider. We don't mind. In fact, we encourage it. Just please don't count the time you spent there against the hour you set aside for this demo! ## Explore the result of `fly launch` Once you're back and/or rested up, explore the app and add-on components. Feel free to review the following in any order, or chose to skip ahead: * [Your Fly App](/deep-dive/application) * [PostgreSQL database](/deep-dive/postgresql) * [Tigris Object Storage](/deep-dive/tigris) * [Upstash Redis](/deep-dive/redis) ## Runtimes Optional, but if you haven't yet dived in, details are provided for each runtime: * [Django](/deep-dive/django) * [Next.js](/deep-dive/nextjs) * [Node.js](/deep-dive/nodejs) * [Phoenix](/deep-dive/phoenix) * [Rails](/deep-dive/rails) *** # Next.js demo reference Source: https://docs.fly.io/deep-dive/nextjs Walk through the Next.js dictaphone source: server.js WebSockets, the app/audio route for clip GET, PUT, and DELETE, pg for Postgres, pubsub.js for Redis. The Next.js demo source is on [GitHub](https://github.com/fly-apps/nextjs-dictaphone). `fly launch` will provide a [`Dockerfile`](https://docs.docker.com/reference/dockerfile/) and a [`fly.toml`](/reference/configuration) config file. When you make changes to your application, you can run `npx @flydotio/dockerfile` to produce updated Dockerfiles. How the pieces are put together: * The original web-dictaphone app is placed in the [Next.js app](https://nextjs.org/docs/app) directory: * [`app/dictaphone.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/dictaphone.js) contains the JavaScript. * [`app/globals.css`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/globals.css) contains the stylesheets. * [`app/page.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/page.js) contains the HTML * [`server.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/server.js) and [`app/audio/[name]/route.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/audio/%5Bname%5D/route.js) contain the server implementation. * The [pg](https://www.npmjs.com/package/pg) module is used to access PostgreSQL. Access to the database is through the `DATABASE_URL` secret. This module is low level which is sufficient for this demo, but most modern Node.js applications use an [ORM](https://amplication.com/blog/top-6-orms-for-modern-nodejs-app-development). * The [AWS SDK for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/welcome.html) is used to access Tigris. The following secrets are used to establish the connection: `AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT_URL_S3`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY`, and `BUCKET_NAME`. * The [express-ws](https://github.com/HenningM/express-ws?tab=readme-ov-file#express-ws-) module is used for [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) support. * The [redis](https://github.com/redis/node-redis?tab=readme-ov-file#node-redis) module is used to access Redis. The `REDIS_URL` secret is used to access the database. Key points of logic: * [`app/page.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/page.js) contains the home page. All of the clip data comes from web sockets. * [`app/dictaphone.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/dictaphone.js) has been modified to make server requests at various points using [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch); the delete and rename actions are split out so that they can be referenced by the React JSX Home page view. * [`server.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/server.js) contains the WebSocket code, and delegates to Next.js to render the rest. * [`pubsub.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/pubsub.js) contains the interface to Redis. It is split out from the server to avoid circular dependencies. * [`app/audio/[name]/route.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/audio/%5Bname%5D/route.js) contains the logic to build responses to requests to GET, PUT, and DELETE audio clips. * [`app/page.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/ac1abcdc9cb1dfa7de08c682b26034e76ad34cc6/app/page.js#L12-L21) contains the client implementation of web sockets. * When the `WHISPER_URL` secret is set, `PUT` requests will cause the audio clips to be passed to the Whisper server, and responses will be used to update the PostgreSQL database. The code for this is in [`app/audio/[name]/route.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/459774ec9ebebdf6f991adaa6c88509854ffe34e/app/audio/%5Bname%5D/route.js#L90-L118). # Node.js demo reference Source: https://docs.fly.io/deep-dive/nodejs Tour the Express.js dictaphone demo: EJS views, the pg module, AWS SDK access to Tigris, express-ws WebSockets, and a Redis queue that updates browsers. The Node.js demo source is on [GitHub](https://github.com/fly-apps/node-dictaphone). `fly launch` will provide a [`Dockerfile`](https://docs.docker.com/reference/dockerfile/) and a [`fly.toml`](/reference/configuration) config file. When you make changes to your application, you can run `npx @flydotio/dockerfile` to produce updated Dockerfiles. How the pieces are put together: * The original web-dictaphone app (minus the HTML) is in the [public](https://github.com/fly-apps/node-dictaphone/tree/main/public) directory, and contains icons, scripts, styles, and a web manifest; all served as static files. * [`views/index.ejs`](https://github.com/fly-apps/node-dictaphone/blob/main/views/index.ejs) contains the HTML template. * [app.js](https://github.com/fly-apps/node-dictaphone/blob/main/app.js) contains the server implementation, based on [Express.js](https://expressjs.com/). There are lots of JavaScript frameworks out there, this is perhaps the oldest and most minimalist. * The [pg](https://www.npmjs.com/package/pg) module is used to access PostgreSQL. Access to the database is through the `DATABASE_URL` secret. This module is low level which is sufficient for this demo, but most modern Node.js applications use an [ORM](https://amplication.com/blog/top-6-orms-for-modern-nodejs-app-development). * The [AWS SDK for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/welcome.html) is used to access Tigris. The following secrets are used to establish the connection: `AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT_URL_S3`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY`, and `BUCKET_NAME`. * The [express-ws](https://github.com/HenningM/express-ws?tab=readme-ov-file#express-ws-) module is used for [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) support. * The [redis](https://github.com/redis/node-redis?tab=readme-ov-file#node-redis) module is used to access Redis. The `REDIS_URL` secret is used to access the database. Key points of logic: * [`public/scripts/app.js`](https://github.com/fly-apps/node-dictaphone/blob/main/public/scripts/app.js) has been modified to make server requests at various points using [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). * [`app.js`](https://github.com/fly-apps/node-dictaphone/blob/main/app.js) contains the logic to build responses to requests for the index page, and to GET, PUT, and DELETE audio clips. * [`pubsub.js`](https://github.com/fly-apps/node-dictaphone/blob/main/pubsub.js) contains the logic to subscribe to a `dictaphone:timestamp` redis queue, and to forward updates to all open websocket connections. * [`websocket.js`](https://github.com/fly-apps/node-dictaphone/blob/main/public/scripts/websocket.js) contains the client implementation of web sockets. When update notifications are received, the index page is re-retrieved and the body of the DOM is replaced with new contents. * When the `WHISPER_URL` secret is set, `PUT` requests will cause the audio clips to be passed to the Whisper server, and responses will be used to update the PostgreSQL database. The code for this is in [app.js](https://github.com/fly-apps/node-dictaphone/blob/1e84a4dece6888dfc68880d146b46511d47391b3/app.js#L102-L129). # Phoenix demo reference Source: https://docs.fly.io/deep-dive/phoenix See how the Phoenix dictaphone demo uses LiveView, Ecto with Postgres, ExAws.S3 for Tigris, and Phoenix.PubSub.Redis, plus the Whisper audio controller. The Phoenix demo source is on [GitHub](https://github.com/fly-apps/phoenix-dictaphone). `fly launch` will provide a [`Dockerfile`](https://docs.docker.com/reference/dockerfile/) and a [`fly.toml`](/reference/configuration) config file. How the pieces are put together: * The original web-dictaphone app is spread throughout your application: * [`assets/js/dictaphone.js`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/assets/js/dictaphone.js) contains the JavaScript. * [`assets/css/dictaphone.css`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/assets/css/dictaphone.css) contains the stylesheets. * [`priv/static/favicon.ico`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/priv/static/favicon.ico) contains your image * [`lib/dictaphone_web/live/dictaphone.html.heex`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/lib/dictaphone_web/live/dictaphone.html.heex) contains the HTML * [`lib/dictaphone_web/live/dictaphone.ex`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/lib/dictaphone_web/live/dictaphone.ex) and [`lib/dictaphone_web/controllers/audio_controller.ex`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/lib/dictaphone_web/controllers/audio_controller.ex) contain the server implementation. * [Ecto.Adapters.Postgres](https://hexdocs.pm/ecto_sql/Ecto.Adapters.Postgres.html) is used to access PostgreSQL. Access to the database is through the `DATABASE_URL` secret. * [ExAws.S3](https://hexdocs.pm/ex_aws_s3/ExAws.S3.html) is used to access Tigris. The following secrets are used to establish the connection: `AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT_URL_S3`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY`, and `BUCKET_NAME`. * [Phoenix.LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html) module is used for [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) support. * [Phoenix.PubSub.Redis](https://hexdocs.pm/phoenix_pubsub_redis/Phoenix.PubSub.Redis.html) is used to access Redis. The `REDIS_URL` secret is used to access the database. Key points of logic: * [`lib/dictaphone_web/live/dictaphone.html.heex`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/lib/dictaphone_web/live/dictaphone.html.heex) contains the home page. * [assets/js/dictaphone.js](https://github.com/fly-apps/phoenix-dictaphone/blob/main/assets/js/dictaphone.js) has been modified to: * upload clips using [the Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) * use [Click Events](https://hexdocs.pm/phoenix_live_view/bindings.html#click-events) for delete * use [Client Hooks](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook) for rename actions the WebSocket code, and delegates to Next.js to render the rest. * [`lib/dictaphone_web/live/dictaphone.ex`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/lib/dictaphone_web/live/dictaphone.ex) contains the interface to Redis. It is split out from the server to avoid circular dependencies. * [`app/audio/[name]/route.js`](https://github.com/fly-apps/nextjs-dictaphone/blob/main/app/audio/%5Bname%5D/route.js) contains the logic to respond to events from both browser clients, and redis pubsub. * [`lib/dictaphone_web/controllers/audio_controller.ex`](https://github.com/fly-apps/phoenix-dictaphone/blob/main/lib/dictaphone_web/controllers/audio_controller.ex) contains the server access to `GET` and `PUT` audio clips. * When the `WHISPER_URL` secret is set, `PUT` requests will cause the audio clips to be passed to the Whisper server, and responses will be used to update the PostgreSQL database. The code for this is in [`lib/dictaphone_web/controllers/audio_controller.ex`](https://github.com/fly-apps/phoenix-dictaphone/blob/396d22a537b4f688f99ce88b707d6e045d828e6f/lib/dictaphone_web/controllers/audio_controller.ex#L42-L58). # Deep Dive: PostgreSQL Source: https://docs.fly.io/deep-dive/postgresql Fly Postgres in the deep dive demo starts as one Machine in its own app. Add an HA cluster and read replicas, and keep it on the private network only. ![Illustration by Annie Ruygt of elephant and a Fly balloon](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/postgresql.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=32573221d6fa9d8f50769cd04bc792d7) Fly Postgres is deployed as a separate app, and that app comes initially configured with a single [Fly Postgres](/unmanaged-postgres) Machine. That's fine for development, but for production you need redundancy and scalability. With a [few commands](/unmanaged-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. You're free to host your database literally anywhere. Before moving on, one last observation on the relational database. While you want and need your application to be on the internet, you are much better off if your relational database is NOT directly exposed to the internet, but can only be accessed via your application. That’s the value of an [internal private network](/networking/private-networking). The private connection from the demo app to the Postgres app was configured automatically for you. # Rails demo reference Source: https://docs.fly.io/deep-dive/rails Rails dictaphone demo internals: Stimulus controllers, Active Record on Postgres, Active Storage on Tigris, Action Cable on Redis, Sidekiq Whisper jobs. The Rails source is on [GitHub](https://github.com/fly-apps/rails-dictaphone/). `fly launch` will provide a [`Dockerfile`](https://docs.docker.com/reference/dockerfile/) and a [`fly.toml`](/reference/configuration) config file. `fly launch` will also work with Rails-provided Dockerfiles. When you make changes to your application, you can run `bin/rails generate dockerfile` to generate updated Dockerfiles. How the pieces are put together: * The original web-dictaphone app distributed throughout the application: * [`app/javascript/controllers/main_controls_controller`](https://github.com/fly-apps/rails-dictaphone/blob/main/app/javascript/controllers/main_controls_controller.js) and [`app/javascript/controllers/sound_clip_controller`](https://github.com/fly-apps/rails-dictaphone/blob/main/app/javascript/controllers/sound_clip_controller.js) contain the JavaScript, implemented as [Stimulus](https://stimulus.hotwired.dev/handbook/origin) controllers. * [`app/assets/stylesheets/dictaphone.css`](https://github.com/fly-apps/rails-dictaphone/blob/main/app/assets/stylesheets/dictaphone.css) contains the stylesheets. * [`app/views/clips/index.html.erb`](https://github.com/fly-apps/rails-dictaphone/blob/main/app/views/clips/index.html.erb) contains the HTML * [Active Record](https://guides.rubyonrails.org/active_record_basics.html) will run using Sqlite3 as the database in development mode, and PostgreSQL in production. Access to the database is through the `DATABASE_URL` secret. * [Active Storage](https://guides.rubyonrails.org/active_storage_overview.html) is configured to write to the filesystem in development and Tigris in production. The following secrets are used to establish the connection: `AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT_URL_S3`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY`, and `BUCKET_NAME`. * [Action Cable](https://guides.rubyonrails.org/action_cable_overview.html) is configured to use an Async adapter in development and the Redis adapter in production. Key points of logic: * [`app/controllers/clips_controller.rb`](https://github.com/fly-apps/rails-dictaphone/blob/main/app/controllers/clips_controller.rb) contains the logic to build responses to requests for the index page, and to GET, PUT, and DELETE audio clips. * The realtime implementation code is primarily in [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html), so all that is needed is one line in [`app/models/clip.rb`](https://github.com/fly-apps/rails-dictaphone/blob/6bdf4f639640c9fb55530546dbbed682b65a7df9/app/models/clip.rb#L2) and one line in [`app/views/layouts/application.html.erb`](https://github.com/fly-apps/rails-dictaphone/blob/6bdf4f639640c9fb55530546dbbed682b65a7df9/app/views/layouts/application.html.erb#L9) * When the `WHISPER_URL` secret is set, `PUT` requests will cause the audio clips to be passed to the Whisper server, and responses will be used to update the PostgreSQL database. This is done using [Active Job](https://guides.rubyonrails.org/active_job_basics.html) and [Sidekiq](https://sidekiq.org/). The code for this is in [`app/jobs/whisper_transcribe_job.rb`](https://github.com/fly-apps/rails-dictaphone/blob/main/app/jobs/whisper_transcribe_job.rb). # Recap Source: https://docs.fly.io/deep-dive/recap Wrap up the Fly.io deep dive after deploying a full-stack app with object storage, realtime updates, and AI, plus links to production, pricing, support. We promised that you could get through this in under an hour. You successfully deployed a full-stack app with both object storage and realtime requirements, and then went on to add AI functionality. Up front, we set a goal: **Help you find out if Fly.io is the right place for you.** Fly.io can support your apps now, with flexible built-in features, and scale with you later as you grow, with low-level control of Machines and powerful compute in 35 [regions](/reference/regions). You're never locked in; you can always eject entirely and move your entire application elsewhere if things don’t work out. How did we do? Visit [community](https://community.fly.io/) and let us know. ## Learn more * [Going to production](/apps/going-to-production) * [Support](/about/support) * [Pricing](/about/pricing) * [Application Monitoring by Sentry](/monitoring/sentry) * [Application Security by Arcjet](/security/arcjet) # Upstash Redis Source: https://docs.fly.io/deep-dive/redis Upstash Redis in the deep dive demo broadcasts pubsub updates to each Machine, which tells browsers over WebSockets. It also caches for Node and Rails. ![Illustration by Annie Ruygt of a puzzle box figure holding two slices of cake](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/redis-upstash.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=6b809776ced663128e2c20a9eb93fa8e) [Upstash Redis](/upstash/redis) is used by the deep dive demo app for its [pubsub](https://redis.io/docs/latest/commands/?group=pubsub) capabilities, but it can do [so much more](https://upstash.com/docs/redis/overall/rediscompatibility). In particular, Redis is useful for caching: * [Node and Redis](https://redis.io/learn/develop/node/nodecrashcourse/caching) * [Rails and Redis](https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-rediscachestore) In the deep dive demo app, updates are broadcast to all Machines via Redis, and then each Machine informs browser clients of the update via [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). The client requests updated information from the application using HTTP GET. # Tigris Source: https://docs.fly.io/deep-dive/tigris Tigris object storage serves the demo's audio files across regions with no setup. Make a bucket public, or migrate from S3 gradually with shadow buckets. ![Illustration by Annie Ruygt Bernard of a blue tiger reading a map with delight](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Tigris-doc.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=830c4f1a0d85fe067be584c0d85b5747) [Tigris](/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. Unlike relational databases, there may be reasons why you want to make your object store available via the internet. In this demo, the object store starts out private, but you can make it [public](/tigris#public-buckets) if you want. Public buckets serve objects without authentication at `https://bucket-name.t3.tigrisfiles.io/key-name`. And if you happen to have an existing S3 object store, check out [shadow buckets](/tigris#migrate-from-another-s3-compatible-provider-with-zero-downtime), which enable you to incrementally migrate your data. # Whisper Source: https://docs.fly.io/deep-dive/whisper Add speech-to-text to the deep dive demo with fly launch --attach, which runs OpenAI Whisper on an L40S GPU Machine that is reachable only over Flycast. Since you are now officially a Fly.io expert, let's dive right in and run the following two commands in the project directory of your deep dive demo app: ``` fly launch --attach --from https://github.com/rubys/cog-whisper fly deploy ``` Next try capturing a new audio clip. It will be transcribed automatically by OpenAI Whisper, an automatic speech recognition (ASR) program that converts speech into text. ## What just happened You provisioned a new Machine, this time with an [L40S](https://www.nvidia.com/en-us/data-center/l40s/) GPU. It will stop when not in use. It will restart when a new request comes in. It is only available on the private network using [Flycast](/networking/flycast). It runs [OpenAI Whisper](https://openai.com/index/whisper/) accessed via a [COG](https://github.com/replicate/cog) interface. This process involves taking audio clips from Tigris, passing them to Whisper, and updating Postgres with the results. The Node code for this is about [two dozen lines of code](https://github.com/fly-apps/node-dictaphone/blob/1e84a4dece6888dfc68880d146b46511d47391b3/app.js#L102-L129), and for Rails is about a [dozen](https://github.com/fly-apps/rails-dictaphone/blob/6bdf4f639640c9fb55530546dbbed682b65a7df9/app/jobs/whisper_transcribe_job.rb#L5-L16). And, as always, there's no lock in. You can opt to replace this with a machine hosted by [Replicate](https://replicate.com/) or elsewhere. # Django: Advanced Guides Source: https://docs.fly.io/django/advanced-guides Advanced Django guides for Fly.io, currently covering staging environments: spin up a separate staging app for each change using GitHub Actions. ## Staging environments with GitHub actions How to create staging environments on the Fly.io with GitHub actions. # Staging environments with GitHub actions Source: https://docs.fly.io/django/advanced-guides/staging-environments-with-github-actions Create a Django staging environment for each pull request on Fly.io with GitHub Actions and fly-pr-review-apps, optionally using a staging Postgres cluster. Creating staging environments for testing changes to our apps can be a challenge. This guide shows how to use GitHub actions to smoothly create a separate staging environment for each pull request using the [`fly-pr-review-apps`](https://github.com/marketplace/actions/pr-review-apps-on-fly-io) action, which will create and deploy our Django project with changes from the specific pull request. It will also destroy it when it's no longer needed, such as after closing or merging a pull request. The entire staging process enclosed in one GitHub action, so we don't have to worry about anything else (**NoOps**). ## Basic flow [GitHub action](https://github.com/features/actions) workflows are defined by YAML files in the `.github/workflows/` directory of our repository. Let's add a new flow that will create and deploy staging environment for each pull request. But first we need to create a new repository secret called `FLY_API_TOKEN` to use for authentication. Go to a GitHub repository page and open: *Settings (tab) → Security → Secrets and variables → Actions → Repository secrets → New repository secret* next, create a new secret called `FLY_API_TOKEN` with a value from: ```bash theme={null} fly tokens org ``` > [!NOTE] > > It's also possible to create a token from the organization dashboard, under the "Tokens" tab. Now, we're ready to add a new flow. This is how we can define it in the `.github/workflows/fly_pr_preview.yml` file: ```yaml theme={null} # .github/workflows/fly_pr_preview.yml name: Start preview app on: pull_request: types: [labeled, synchronize, opened, reopened, closed] concurrency: group: ${{ github.workflow }}-pr-${{ github.event.number }} cancel-in-progress: true permissions: contents: read env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} jobs: preview-app: if: contains(github.event.pull_request.labels.*.name, 'PR preview app') runs-on: ubuntu-latest name: Preview app environment: name: pr-${{ github.event.number }} url: ${{ steps.deploy.outputs.url }} steps: - name: Checkout uses: actions/checkout@v4 - name: Deploy preview app uses: superfly/fly-pr-review-apps@1.2.0 id: deploy with: region: waw org: personal ``` It's time to split our configuration up into its component parts: * `on → pull_request → types`: specifies [events](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) on which a new staging project will be deployed: ```yaml theme={null} pull_request: types: [labeled, synchronize, opened, reopened, closed] ``` * `concurrency`: prevents concurrent deploys for the same PR (Pull Request). The `group` name contains a PR number and workflow name to create a separate group for each workflow and PR: ```yaml theme={null} concurrency: group: ${{ github.workflow }}-pr-${{ github.event.number }} cancel-in-progress: true ``` * `env`: makes the `FLY_API_TOKEN` secret available to use for authentication: ```yaml theme={null} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} ``` * `jobs → preview-app → if`: skips deploy on PRs without the *PR preview app* label. Both for safety reasons and to avoid creating a staging environments when no needed: ```yaml theme={null} if: contains(github.event.pull_request.labels.*.name, 'PR preview app') ``` * `jobs → preview-app → environment`: describes the deployment target to show up in a pull request UI. `steps.deploy.outputs.url` is filled by the `superfly/fly-pr-review-apps` and will contain the URL of a deployed staging project: ```yaml theme={null} environment: name: pr-${{ github.event.number }} url: ${{ steps.deploy.outputs.url }} ``` * `jobs → preview-app → steps → with`: specifies all inputs that we want to pass to our flow. You can check available options in the [README](https://github.com/superfly/fly-pr-review-apps#inputs). We pass the Fly.io [region](/reference/regions#fly-io-regions) and organization as a starting point: ```yaml theme={null} with: region: waw org: personal ``` The action configured in this way will deploy an app with the name created according to the following pattern: `pr-{{ PR number }}-{{ repository owner }}-{{ repository name }}` to the: `https://{{ app name }}.fly.dev`, e.g. `https://pr-9-felixxm-fly-pr-preview-example.fly.dev/` (for PR number 9 in the repository called `pr-preview-example`). ## Using Postgres cluster Using a dedicated staging database is a good practice for test environments. This gives us more control and appropriate separation from the production environment which eliminates a potential data leak vector. If you don't have a [Postgres cluster](/unmanaged-postgres) specifically for testing purposes you can create one with [`fly postgres create`](/flyctl/cmd/fly_postgres_create): ```bash theme={null} fly postgres create --name pg-fly-pr-staging-preview ``` Once created, we can specify it in our action (`jobs → preview-app → steps → with`) using the `postgres` input: ```yaml theme={null} # .github/workflows/fly_pr_preview.yml name: Start preview app on: pull_request: types: [labeled, synchronize, opened, reopened, closed] concurrency: group: ${{ github.workflow }}-pr-${{ github.event.number }} cancel-in-progress: true permissions: contents: read env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} jobs: preview-app: if: contains(github.event.pull_request.labels.*.name, 'PR preview app') runs-on: ubuntu-latest name: Preview app environment: name: pr-${{ github.event.number }} url: ${{ steps.deploy.outputs.url }} steps: - name: Checkout uses: actions/checkout@v4 - name: Deploy preview app uses: superfly/fly-pr-review-apps@1.2.0 id: deploy with: postgres: pg-fly-pr-staging-preview # ← Added region: waw org: personal ``` With that in place, our staging Postgres cluster will be automatically attached to the test app, which will make a `DATABASE_URL` environment variable available in the test VM. ## Additional release steps For performing additional release steps when deploying our staging environment, we can use a custom [Fly.io TOML configuration file](/reference/configuration) dedicated for staging with a release script [to run before a deployment](/reference/configuration#run-one-off-commands-before-releasing-a-deployment). First, make a copy of an existing configuration: ```bash theme={null} mkdir staging ``` ```bash theme={null} cp fly.toml staging/fly_staging.toml ``` Next, create a script (`staging/post_deploy.sh`) to prepare our staging database: ```bash theme={null} # staging/post_deploy.sh #!/usr/bin/env bash # Migrate database. python /code/manage.py migrate # Load fixtures with test data. python /code/manage.py loaddata /code/staging/test_groups.json ``` Finally, we need to add `release_command` to the TOML configuration calling our script: ```toml theme={null} # staging/fly_staging.toml console_command = "/code/manage.py shell" [build] [env]  PORT = "8000" [deploy]   release_command = "sh ./staging/post_deploy.sh" # ← Added. ... ``` and specify the custom TOML configuration in our action (`jobs → preview-app → steps → with`) using the `config` input: ```yaml theme={null} # .github/workflows/fly_pr_preview.yml name: Start preview app on: pull_request: types: [labeled, synchronize, opened, reopened, closed] concurrency: group: ${{ github.workflow }}-pr-${{ github.event.number }} cancel-in-progress: true permissions: contents: read env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} jobs: preview-app: if: contains(github.event.pull_request.labels.*.name, 'PR preview app') runs-on: ubuntu-latest name: Preview app environment: name: pr-${{ github.event.number }} url: ${{ steps.deploy.outputs.url }} steps: - name: Checkout uses: actions/checkout@v4 - name: Deploy preview app uses: superfly/fly-pr-review-apps@1.2.0 id: deploy with: config: staging/fly_staging.toml # ← Added postgres: pg-fly-pr-staging-preview region: waw org: personal ``` With this small effort we have staging database created on Fly.io! 🚨 Be aware that `release_command` is run on a temporary VM and cannot modify the local storage or state. It's fine to run database operations but not to perform release steps that attempt to modify a local storage, e.g. collecting static files. Such steps should be added to the `Dockerfile`. For example, if we want to collect static files, then add the `collectstatic` management command to the `Dockerfile`: ```docker theme={null} ARG PYTHON_VERSION=3.10-slim-bullseye FROM python:${PYTHON_VERSION} ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 RUN mkdir -p /code WORKDIR /code COPY requirements.txt /tmp/requirements.txt RUN set -ex && \ pip install --upgrade pip && \ pip install -r /tmp/requirements.txt && \ rm -rf /root/.cache/ COPY . /code # ↓ Added ↓ RUN set -ex && \ python /code/manage.py collectstatic --noinput EXPOSE 8000 CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "hello_pr_preview_example.wsgi"] ``` # Deploy an Existing Django App Source: https://docs.fly.io/django/getting-started/existing Move an existing Django app to Fly.io: configure settings, launch with fly launch, attach Postgres, and troubleshoot your first deploy. If you have an existing Django app that you want to move over to Fly, this guide walks you through the initial deployment process and shows you techniques you can use to troubleshoot issues you may encounter. ## Configure Django The official [How to Deploy Django](https://docs.djangoproject.com/en/stable/howto/deployment/) guide and [Django Deployment Checklist](https://docs.djangoproject.com/en/stable/howto/deployment/checklist/) are a good first step to ensure your app is ready for deployment. The third-party package [WhiteNoise](https://whitenoise.evans.io/en/stable/) is recommended for serving static files in production as this requires [additional configuration](https://docs.djangoproject.com/en/stable/howto/static-files/deployment/). A production web server must be installed, typically either [Gunicorn](https://docs.gunicorn.org/en/latest/install.html) or [uWSGI](https://uwsgi-docs.readthedocs.io/en/latest/). The database adapter [Psycopg](https://www.psycopg.org/psycopg3/docs/) is commonly used along with [dj-database-url](https://pypi.org/project/dj-database-url/) to establish a Django database connection via a `DATABASE_URL` environment variable. Be sure to have generated an up-to-date `requirements.txt` file for any new packages added for deployment. ## flyctl Fly.io has its own command-line utility for managing apps, [flyctl](/flyctl). If not already installed, follow the instructions on the [installation guide](/flyctl/install) and [log in to Fly](/getting-started/sign-up-sign-in). ## Provision Django and Postgres Servers To configure and launch the app, use the command `fly launch` and follow the wizard. You can set a name for the app, choose a default region, launch and attach a Postgres database. You can also set up a Redis database though we will not be doing so in this example. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Django app Creating app in ~/django-existing-app We're about to launch your Django app on Fly.io. Here's what you're getting: Organization: Jane Smith (fly launch defaults to the personal org) Name: django-existing-app (derived from your directory name) Region: Ashburn, Virginia (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes Opening https://fly.io/cli/launch/mo1ootho9ualooghoch3iih6cha2shah ... Waiting for launch data... Done Created app 'django-existing-app' in organization 'personal' Admin URL: https://fly.io/apps/django-existing-app Hostname: django-existing-app.fly.dev Set secrets on django-existing-app: SECRET_KEY Creating postgres cluster in organization personal Creating app... Setting secrets on app django-existing-app-db... Provisioning 1 of 1 machines with image flyio/postgres-flex:15.3@sha256:44b698752cf113110f2fa72443d7fe452b48228aafbb0d93045ef1e3282360a6 Waiting for machine to start... Machine 217811c53e0896 is created ==> Monitoring health checks Waiting for 217811c53e0896 to become healthy (started, 3/3) Postgres cluster django-existing-app-db created Username: postgres Password: lt5JoEIVons5INJ Hostname: django-existing-app-db.flycast Flycast: fdaa:3:9f45:0:1::2 Proxy port: 5432 Postgres port: 5433 Connection string: postgres://postgres:lt5JoEIVons5INJ@django-existing-app-db.flycast:5432 Save your credentials in a secure place -- you won't be able to see them again! Connect to postgres Any app within the Jane Smith organization can connect to this Postgres using the above connection string Now that you've set up Postgres, here's what you need to understand: https://fly.io/docs/postgres/getting-started/what-you-should-know/ Checking for existing attachments Registering attachment Creating database Creating user Postgres cluster django-existing-app-db is now attached to django-existing-app The following secret was added to django-existing-app: DATABASE_URL=postgres://django_existing_app:lt5JoEIVons5INJ@django-existing-app-db.flycast:5432/django_existing_app?sslmode=disable Postgres cluster django-existing-app-db is now attached to django-existing-app Wrote config file fly.toml [INFO] Python 3.10.12 was detected. 'python:3.10-slim-bullseye' image will be set in the Dockerfile. Validating ~/django-existing-app/fly.toml Platform: machines ✓ Configuration is valid Your Django app is ready to deploy! For detailed documentation, see https://fly.dev/docs/django/ ``` ## Dockerfile and fly.toml The `fly launch` command creates two new files in the project that are automatically configured: `Dockerfile` and `fly.toml`. The [Dockerfile](https://docs.docker.com/engine/reference/builder/) is essentially instructions for creating an image. The [`fly.toml`](/reference/configuration) file is used by Fly.io to configure applications for deployment. Configuration of builds, environment variables, internet-exposed services, disk mounts and release commands go here. ## ALLOWED\_HOSTS & CSRF\_TRUSTED\_ORIGINS The dedicated URL for your deployment will be `.fly.dev`. Update the [ALLOWED\_HOSTS](https://docs.djangoproject.com/en/stable/ref/settings/#allowed-hosts) and [CSRF\_TRUSTED\_ORIGINS](https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins) configurations with your `` to include it. ## Deploy Your Application To deploy the application use the following command: ```bash theme={null} fly deploy ``` This will take a few seconds as it uploads your application, verifies the app configuration, builds the image, and then monitors to ensure it starts successfully. Once complete, visit your app with the following command: ```bash theme={null} fly apps open ``` If everything went as planned you will see your Django application homepage. ## Troubleshooting your initial deployment Since this is an existing Django app, it is highly likely it might not boot because you probably need to configure secrets or other service dependencies. Let's walk through how to troubleshoot these issues so you can get your app running. ### View Log Files If your application didn't boot on the first deploy, run `fly logs` to see what's going on. ```bash theme={null} fly logs ``` This shows the past few log file entries and tails your production log files. [Additional flags](/flyctl/cmd/fly_logs) are available for filtering. ### Console To SSH into your hosted Django application use the command `fly ssh console`. For example, to execute Django's `createsuperuser` command to log into the admin do the following: ```bash theme={null} fly ssh console # python manage.py createsuperuser ``` If you prefer, this can be run as one command instead: ```bash theme={null} fly ssh console --pty -C 'python /code/manage.py createsuperuser' ``` > [!NOTE] > > The `--pty` flag tells the SSH server to run the command in a pseudo-terminal, which `createsuperuser` requires. ### Secrets Secrets allow sensitive values, such as credentials and API keys, to be securely passed to your Django applications. You can set, remove, or list all secrets with the [fly secrets](/apps/secrets) command. ### Git Deployments are initiated via the `fly deploy` command--git isn't needed to deploy to Fly.io. The advantage of this approach is that your git history will be clean and not full of `git push` commits such as occurs on other hosting platforms. This also means that `.gitignore` files are not ignored. If you have secrets or other sensitive information in your git history, it is recommended to create a `.dockerignore` file and add the git repo there. ### Databases Fly.io has a [Postgres offering](/unmanaged-postgres) to automate provisioning, maintenance, and snapshot tasks for your Postgres database, but it does not manage it. If you run out of disk space, RAM, or other resources on your Fly Postgres instances, you'll have to scale those virtual machines from the Fly CLI. If you prefer, you can instead connect to an [external fully-managed Postgres database](/postgres). ### Custom Domain & SSL Certificates After you finish deploying your application to Fly.io and have tested it extensively, [read through the Custom Domain docs](/networking/custom-domain) and point your domain at Fly. In addition to supporting [`CNAME` DNS records](/networking/custom-domain#cname-records), Fly.io also supports [`A` and `AAAA` records](/networking/custom-domain#a-and-aaaa-records-recommended) for those who want to point `example.com` (without the `www.example.com`) directly at Fly. ### flyctl Commands The [flyctl CLI docs](/flyctl) have an extensive inventory of `fly` commands. Here are a few common commands especially if you are coming from another hosting service like Heroku. | Task | Command | | - | - | | Log in | `fly login` | | Launch an app | `fly launch` | | Deployments | `fly deploy` | | Open web dashboard | `fly dashboard` | | SSH | `fly ssh console` | | Tail log files | `fly logs` | | Secrets | `fly secrets` | | View releases | `fly releases` | | Help | `fly help` | # Deploy a Django App Source: https://docs.fly.io/django/getting-started/index Deploy a Django app to Fly.io. Build a sample app, generate a Dockerfile with fly launch, serve it with Gunicorn, and add Postgres. ![Green book titled Django on a shelf with other books and a plant](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/django-intro.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=e1519c17fc0c55ca2beaeead0f2f5d6d) In this guide we recreate and deploy [this Django application](https://github.com/fly-apps/hello-django) to demonstrate how quickly Django apps can be deployed to Fly.io! ## Initial Local Setup Make sure that [Python](https://www.python.org/) is already installed on your computer along with a way to create virtual environments. This allows you to run your project locally, and test that it works, before deploying it to Fly.io. > We recommend the latest [supported versions](https://devguide.python.org/versions/#supported-versions) of Python. Create a folder for your project. Here we'll call it `hello-django`. Enter the folder with `cd`: ```bash theme={null} mkdir hello-django && cd hello-django ``` ### Virtual Environment For this guide, we use [venv](https://docs.python.org/3/library/venv.html#module-venv) but any of the other popular choices such as [Poetry](https://python-poetry.org/), [Pipenv](https://github.com/pypa/pipenv), or [pyenv](https://github.com/pyenv/pyenv) work too. ```shell theme={null} # Unix/macOS $ python3 -m venv .venv $ source .venv/bin/activate (.venv) $ ``` ```powershell theme={null} # Windows (PowerShell) > python -m venv .venv > .venv\Scripts\Activate.ps1 (.venv) ...> ``` > [!NOTE] > > From this point on, the commands won't be displayed with `(.venv)` but we assume you have your Python virtual environment activated. ### Install Django With your virtual environment **activated**, install the [latest](https://www.djangoproject.com/download/#supported-versions) version of Django using [pip](https://pip.pypa.io/en/stable/): ```bash theme={null} python -m pip install Django ``` ### Create a Django Project Inside the `hello-django` folder, create a Django project named `hello_django`: ```bash theme={null} django-admin startproject hello_django . ``` > Don't forget the `.` at the end. It's crucial because it tells the script to create the Django project directory structure in the current directory, our folder `hello-django`. Note that by convention, we name Django projects using `snake_case`: words written in lowercase with spaces replaced by underscore (`_`). Hyphens (`-`) are not valid identifiers and you might get this error message: ```terminal theme={null} 'hello-django' is not a valid project name. Please make sure the name is a valid identifier. ``` By this point, our project structure should look like this: ```terminal theme={null} hello-django/ |-- .venv |-- hello_django/ | |-- __init__.py | |-- asgi.py | |-- settings.py | |-- urls.py | |-- wsgi.py |-- manage.py ``` ### Create and Map a URL to a View Now let's configure a basic view that returns the text, `Hello, Fly!` by updating the [`hello_django/urls.py`](https://github.com/fly-apps/hello-django/blob/main/hello_django/urls.py) file: ```python theme={null} # hello_django/urls.py from django.contrib import admin from django.http import HttpResponse from django.urls import path # ↓ New basic view returning "Hello, Fly!" ↓ def hello(request): return HttpResponse("Hello, Fly!") urlpatterns = [ path("admin/", admin.site.urls), path("", hello, name="hello"), # ← Added! ] ``` ### Run migrations As part of Django's core functionality, some existing apps are included by default to provide you with out-of-the-box features. Some of those apps require their own database tables. To initialize the local database and set up those tables, run the `migrate` command: ```bash theme={null} python manage.py migrate ``` ### Start the Web Server Now `runserver` to start up Django's local web server: ```bash theme={null} python manage.py runserver ``` If you open `http://127.0.0.1:8000/` in your web browser it now displays the text `Hello, Fly!`. ## Django Deployment Checklist By default, Django is configured for local development. The [How to Deploy Django](https://docs.djangoproject.com/en/stable/howto/deployment/) and [Django Deployment Checklist](https://docs.djangoproject.com/en/stable/howto/deployment/checklist/) guide list the various steps required for a secure deployment. > You can also find a complete guide [Deploying Django to Production](https://fly.io/django-beats/deploying-django-to-production/) in our [Django Beats Blog](https://fly.io/django-beats/). However, for demonstration purposes, we can take some shortcuts. First, in the [`hello_django/settings.py`](https://github.com/fly-apps/hello-django/blob/main/hello_django/settings.py) file update the `ALLOWED_HOSTS` configuration to accept a host on which it's deployed. Use the [`FLY_APP_NAME`](/machines/runtime-environment#fly_app_name) environment variable for that: ```python theme={null} # hello_django/settings.py import os APP_NAME = os.environ.get("FLY_APP_NAME") ALLOWED_HOSTS = [f"{APP_NAME}.fly.dev"] # ← Updated! ``` Second, install [Gunicorn](https://gunicorn.org/) as our [production server](https://docs.djangoproject.com/en/dev/ref/django-admin/#runserver): ```bash theme={null} python -m pip install gunicorn ``` Third, create a [`requirements.txt`](https://github.com/fly-apps/hello-django/blob/main/requirements.txt) file listing all the packages in the current Python virtual environment: ```bash theme={null} pip freeze > requirements.txt ``` That's it! We're ready to deploy on Fly.io. ## flyctl Fly.io has its own command-line utility for managing apps, [flyctl](/flyctl). If not already installed, follow the instructions on the [installation guide](/flyctl/install) and [log in to Fly.io](/getting-started/sign-up-sign-in). ## Configure and Deploy your Fly App To configure and launch the app, run the `fly launch` command and follow the wizard. You can set a name for the app and choose your primary region. You can also choose to launch and attach a Postgres database and/or a Redis database though we are not using either in this example. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Django app Creating app in /flyio/hello-django We're about to launch your Django app on Fly.io. Here's what you're getting: Organization: Joe Doe (fly launch defaults to the personal org) Name: hello-django (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes Opening https://fly.io/cli/launch/mo1ootho9ualooghoch3iih6cha2shah ... Waiting for launch data... Done Created app 'hello-django' in organization 'personal' Admin URL: https://fly.io/apps/hello-django Hostname: hello-django.fly.dev Set secrets on hello-django: SECRET_KEY Wrote config file fly.toml [INFO] Python 3.10.12 was detected. 'python:3.10-slim-bullseye' image will be set in the Dockerfile. Validating /flyio/hello-django/fly.toml Platform: machines ✓ Configuration is valid Your app is ready! Deploy with `flyctl deploy` ``` This creates two new files in the project that are automatically configured: a [`Dockerfile`](https://github.com/fly-apps/hello-django/blob/main/Dockerfile) and [`fly.toml`](https://github.com/fly-apps/hello-django/blob/main/fly.toml) file to configure applications for deployment. To deploy the application use the following command: ```bash theme={null} fly deploy ``` This will take a few seconds as it uploads your application, verifies the app configuration, builds the image, and then monitors to ensure it starts successfully. Once complete visit your app with the following command: ```bash theme={null} fly apps open ``` YAY! You are up and running! Wasn't that easy? ## Recap We started with an empty directory and in a matter of minutes had a running Django application deployed to the web. A few things to note: * Your application is running on a Virtual Machine that was created based on the `Dockerfile` image. * The `fly.toml` file controls your app configuration and can be modified as needed. * `fly dashboard` can be used to monitor and adjust your application. Pretty much anything you can do from the browser window you can also do from the command line using [`fly` commands](/flyctl). Try `fly help` to see what you can do. Now that you have seen how to deploy a simple Django application, it is time to move on to [Existing Django Apps](/django/getting-started/existing) that feature static files and a Postgres database. # Django on Fly.io Source: https://docs.fly.io/django/index Run Django on Fly.io: fly launch detects and deploys Django apps. Start with the starter app guide, move an existing project off Heroku, or go advanced. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/django-intro.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=e1519c17fc0c55ca2beaeead0f2f5d6d) Fly.io is a great place to run your Django application. `fly launch` can detect, configure and deploy Django apps. If you have questions or comments, please reach out on the [Django category of the Community Board](https://community.fly.io/c/django/) for Django on Fly.io specific discussions. The general [Community Board](https://community.fly.io/) is great for all things Fly.io: networking, databases, regions, and more. You can use the `django` tag as well to help interested people find a topic more easily. ## [Getting Started](./getting-started) Run through the [Starter Django App](./getting-started/) guide to get a feel for what it's like to deploy to Fly.io. You'll have a Django app running in just a few minutes. ## [Existing Django Apps](./getting-started/existing/) The [Existing Django Apps](./getting-started/existing/) guide walks through deploying a production-ready project to Fly.io. If you are coming from another hosting platform like Heroku there are additional tips for managing the migration. ## [Advanced Guides](./advanced-guides) # Elixir: Advanced Guides Source: https://docs.fly.io/elixir/advanced-guides Advanced Elixir on Fly.io: GitHub Actions CI/CD, Observer and Livebook against production, clustering from your laptop, and SQLite3 as a database. Elixir can do some pretty neat things by inheriting abilities from the Erlang Virtual Machine (BEAM). This provides a set of advanced guides for Elixir applications running on Fly. ## GitHub Actions for Elixir CI/CD Setup GitHub CI/CD for your Elixir project. ## Connecting Observer to Your App in Production Guide for connecting a locally running Observer instance to your application running on Fly. ## Connecting Livebook to Your App in Production Guide for connecting a locally running Livebook to your application running on Fly. ## Do Interesting Things with Livebook and Your Production App Guide shows interesting things we can do when Livebook connects to our deployed app running on Fly. ## Easy Clustering from Home to Fly.io Guide for connecting a locally running Elixir application to another application running on Fly. ## SQLite3 This guide shows you how to use SQLite3 as your database # Easy Clustering from Home to Fly.io Source: https://docs.fly.io/elixir/advanced-guides/clustering-from-home-to-your-app-in-fly Cluster a local Elixir node with an Elixir app running on Fly.io over WireGuard, with cookie and IPv6 setup plus a bash script that automates the connection. This explains how to cluster a locally running Elixir application with another Elixir application running on Fly.io. Additionally, a [**bash script** (named `cluster_with_remote`)](https://gist.github.com/brainlid/9e02e95f7d9c65a23312a4df95094d2a) is provided to automate the process of starting the local node and clustering it with the server. Here we cover *why* we might want to do this, *what* is required to make it work, and *how* to make it happen. ## Why cluster a local application with the one on the server? Besides being really cool that we can do this, there are some practical reasons as well. ### Develop and debug a distributed application Building a globally distributed application can be challenging to model locally. With [Fly.io Regions](/reference/regions), we can deploy our cluster-aware application where it makes sense. Then, our local application joins the global cluster, giving us a close-up view of how the application behaves in a truly globally distributed environment. ## How it works Elixir supports [clustering](/elixir/the-basics/clustering) multiple running Elixir applications together (thanks to [Erlang](https://www.erlang.org/doc/reference_manual/distributed.html)), even while running on separate machines. Fly.io makes creating a [WireGuard VPN tunnel](/networking/private-networking#private-network-vpn) between your local machine and your Fly.io organization easy. ![Image showing an app inside a house connecting to a wire that plugs into a Fly balloon with a region abbreviation on it.](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/cluster-from-home-to-fly-app-1.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=f72f0fc1fb798f7d836bb1e31b1f4266) This means we can connect and cluster our locally running Elixir application with an Elixir application deployed at Fly.io. There are some special configuration requirements needed to make this possible, but we cover it all here. ### Prerequisites There are a few prerequisites to consider when clustering a local Elixir application to one running in Fly.io. We'll provide tools or instructions to help with each. 1. A [WireGuard connection](/networking/private-networking#private-network-vpn) must be setup and open from your local machine to Fly.io 2. The same version of Elixir and Erlang OTP must be running on both ends. 3. The locally running Elixir application needs the Erlang COOKIE used on the server. 4. We need the full node name of the Elixir node running on the server (**NOTE:** The bash script provided later does this for you.) 5. The local Elixir application must be started with IPv6 networking support enabled. By default it does not. ### Setting the Erlang cookie The [recommended way for setting the Erlang COOKIE](/elixir/the-basics/clustering#making-the-cookie-changes) value in your deployed application is to set an ENV named `RELEASE_COOKIE`. This gives the server a stable, predictable cookie value. In order for the nodes to connect, they need the same cookie. When [set in the ENV through Fly.io](/flyctl/cmd/fly_config_env), we can read it from the application's information. The script does this so we don't have to do anything else. ### Starting with IPv6 support Fly.io uses an IPv6 network internally for private IPs. The BEAM needs IPv6 support to be enabled explicitly. On the server, that’s taken care of through a Dockerfile. Locally, however, it needs to be enabled so the local application can cluster with the remote node. The issue is, if IPv6 support is enabled globally, like in a `.bashrc` file, then setting it in the `cluster_with_remote` script essentially flips it OFF. If NOT set globally, then it should be set in the script. Choose the approach that best fits your situation. When set globally in your `.bashrc` file (recommended), it looks like this: ``` export ERL_AFLAGS="-kernel shell_history enabled -proto_dist inet6_tcp" ``` This one includes the added benefit of turning on shell history in IEx. Yay! If not set globally, it can be set in the script command like this: ``` iex --erl "-proto_dist inet6_tcp" --sname local [...] ``` Where the `--erl "-proto_dist inet6_tcp"` portion is the key. ### Hidden by default: A note about production systems Joining your local application to a production cluster in a public way may result in other application sending work, tasks, or executing processes in your local node, depending on the behavior of your specific application. This may not be what you want! For this reason, the script uses the `--hidden` option to hide the local node from the rest of the cluster. To see the ***all*** the connected nodes, including any hidden ones, use: ```elixir theme={null} Node.list(:hidden) ``` > [!NOTE] > > If you want the local Elixir application to be *fully visible* to the rest of the cluster, remove the `--hidden` argument in the bash script. ## Bash script file Create [this file](https://gist.github.com/brainlid/9e02e95f7d9c65a23312a4df95094d2a) locally in your root of your Elixir project. ```sh expandable theme={null} #!/bin/bash # After opening a WireGuard connection to your Fly.io network, copy this file # into the directory for your Elixir application. Run the script to start the # local Elixir project and cluster it to an application running on Fly.io. # In order for this to work: # - Your wireguard connection must be up. # - When run from a directory with a `fly.toml` file, `flyctl` command is used # to access information about the application. # - Set the ENV `CLUSTER_APP_NAME` to specify a different hosted app name. # - Set the ENV `RELEASE_COOKIE` to override the Erlang cookie used for # clustering. It uses the the value from the deployed app if it is set there. # - Run the script. set -e if ! command -v jq &> /dev/null; then echo "jq is not installed. Please install it before running this script. It is a command-line JSON processor." exit 1 fi # Check if CLUSTER_APP_NAME is set and use it if found if [[ -n $CLUSTER_APP_NAME ]]; then # Use the override app_name from the ENV json_data=$(fly status --app ${CLUSTER_APP_NAME} --json) else # Use the app_name for the current app json_data=$(fly status --json) fi # Use an explicit RELEASE_COOKIE value if provided if [ -n "$RELEASE_COOKIE" ]; then release_cookie=$RELEASE_COOKIE else # Extract the RELEASE_COOKIE value from the deployed app release_cookie=$(echo "$json_data" | jq -r '.Machines[] | select(.state == "started") | .config | .env | .RELEASE_COOKIE' | head -n 1) if [ $release_cookie == "null" ]; then echo "The deployed application did not set RELEASE_COOKIE ENV. If the cookie is static on the server, provide it locally through RELEASE_COOKIE." exit 1 fi fi # Extract the app_name app_name=$(echo "$json_data" | jq -r '.Name') # Extract private_ip for the first started machine private_ip=$(echo "$json_data" | jq -r '.Machines[] | select(.state == "started") | .private_ip' | head -n 1) # Extract image_ref tag hash for the first started machine image_tags=$(echo "$json_data" | jq -r '.Machines[] | select(.state == "started") | .image_ref.tag | sub("deployment-"; "")' | head -n 1) if [ -z "$private_ip" ]; then echo "No instances appear to be running at this time." exit 1 fi # Assemble the full node name full_node_name="${app_name}-${image_tags}@${private_ip}" echo Attempting to connect to $full_node_name # IMPORTANT: # ========== # Fly.io uses an IPv6 network internally for private IPs. The BEAM needs IPv6 # support to be enabled explicitly. # # The issue is, if it's enabled globally like in a `.bashrc` file, then setting # it here essentially flips it OFF. If not set globally, then it should be set # here. Choose the version that fits your situation. # # It's the `--erl "-proto_dist inet6_tcp"` portion. # export ERL_AFLAGS="-kernel shell_history enabled -proto_dist inet6_tcp" # Toggles on IPv6 support for the local node being started. # iex --erl "-proto_dist inet6_tcp" --sname local --cookie ${release_cookie} --hidden -e "IO.inspect(Node.connect(:'${full_node_name}'), label: \"Node Connected?\"); IO.inspect(Node.list(:hidden), label: \"Connected Nodes\")" -S mix phx.server # Does NOT toggle on IPv6 support, assuming it is enabled some other way. iex --sname local --cookie ${release_cookie} --hidden -e "IO.inspect(Node.connect(:'${full_node_name}'), label: \"Node Connected?\"); IO.inspect(Node.list(:hidden), label: \"Connected Nodes\")" -S mix phx.server ``` Make the script file executable. ``` chmod +x cluster_with_remote ``` ## Script usage With the perquisites out of the way, we'll use the `cluster_with_remote` script to start our local Elixir application. The script automates much of what needs to be done to make the process work smoothly. Feel free to customize the script as needed. The script is designed to be copied into a project with little to no modification required. The only expected customizations are handled through ENV values that can be controlled per-project. There are two primary ways to use the script: 1. Cluster the local application to the same project deployed at Fly.io. This is the same application running in two places. 2. Cluster a local application to a difference project deployed at Fly.io. The deployed application's name must be provided. ### Cluster to the same application running on the server The simplest variation is when we cluster our local application to a deployed version of itself. The `fly.toml` file in the directory with a copy of the `cluster_with_remote` is for the deployed application. To do this, just execute the script: ``` ./cluster_with_remote ``` The script outputs if the clustering connection succeeded and prints the names of the connected nodes. ### Cluster to a different application running on the server There are times when the local application is connecting to a *different* application than where the script is running from. To do this, we need to tell the script the Fly.io app name of the application we want to connect with. It can be done like this: ``` CLUSTER_APP_NAME=server-app-name ./cluster_with_remote ``` This can also be set as a project-specific ENV using a tool like [direnv](https://direnv.net/) or [dotenv](https://www.dotenv.org/). The custom app name to cluster with can be written to env file and then running the script just works. ``` ./cluster_with_remote ``` If the Erlang cookie of the deployed application is not set using the recommended `RELEASE_COOKIE` ENV setting, it can still be provided to the script using a local ENV named `RELEASE_COOKIE`. See the script for details. ![Image showing an app inside a house connecting to a wire that plugs into a Fly balloon with a region abbreviation on it. There are three balloons in the sky and for different regions and they are connected by wires.](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/cluster-from-home-to-fly-app-2.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=aaf4ee67de52519880f37d27e70d9cbf) Now you're *really* doing distributed Elixir! ## Summary When we couple Elixir's clustering ability with Fly.io's networking, VPN, and API discoverability, we can easily cluster a locally running Elixir application with a deployed Elixir application. This makes it easy to develop and debug distributed applications. The ready-to-use script automates much of the process. # Connecting Livebook to Your App in Production Source: https://docs.fly.io/elixir/advanced-guides/connect-livebook-to-your-app Connect a local Livebook to your production Elixir app on Fly.io over WireGuard, covering Elixir version requirements, the node name, and the shared cookie. [Livebook](https://github.com/elixir-nx/livebook) is an exciting advancement in the Elixir community. It was created for the Elixir machine learning library [Nx](https://github.com/elixir-nx/nx). You can think of it as Elixir's version of [Jupyter Notebooks](https://jupyter.org/). Livebook ends up being very flexible and powerful. Because Elixir nodes can easily be clustered together, you can run Livebook on your local machine, connect it to your Elixir servers, and do some really interesting things. Visualizing the setup: ![WireGuard livebook connection](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/elixir-livebook-to-server-fly-overview.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=95b2989ef2caf9d8cc4d0f9d293c8af0) ## Requirements There are a few requirements for this to work. * **Livebook requires Elixir version 1.14.2 or higher**. Livebook runs locally on your machine, so that part is easy to control. However, the **server** side needs to have the same version of Elixir as well. When Livebook connects to the server, it loads some code into the server environment as well. So your server version of Elixir also needs to be 1.14.2 or higher. I recommend making your local version be the same as the production version to reduce potential problems. * **Known cookie value on your server**. Livebook needs to know the cookie value used on the server. Follow [this guide to give your app a static cookie value](/elixir/the-basics/clustering#the-cookie-situation). * **WireGuard setup on your local machine**. Follow the Fly.io [Private Network VPN](/networking/private-networking#private-network-vpn) guide to walk through that. > [!NOTE] > > **Elixir Version Tip** > > Check the version of Elixir and Erlang specified in your Dockerfile for the deploy. That's the actual version of Elixir your server is running. > > You may need to update the version in your Dockerfile and deploy the application before continuing. ## Installing Livebook There are several ways to [install and run Livebook](https://livebook.dev/#install). [Livebook Desktop](https://news.livebook.dev/introducing-the-livebook-desktop-app-4C8dpu) is really slick and deploying a [Livebook on Fly](https://fly.io/launch/livebook) is super easy. Livebook Desktop can be installed as an app on Mac and Window. For Linux, we can pull the source or install it as an [Escript](https://github.com/elixir-nx/livebook#escript). ### Using Livebook Desktop When running [Livebook Desktop](https://livebook.dev/#install), Livebook will invoke on boot a file named `~/.livebookdesktop.sh` on macOS or `%USERPROFILE%\.livebookdesktop.bat` on Windows. This file can be modified to set environment variables used by Livebook, such as: ``` export LIVEBOOK_DISTRIBUTION=name export ERL_AFLAGS="-proto_dist inet6_tcp" ``` ### Install as an Escript This section assumes you are already setup for [local Elixir development](https://elixir-lang.org/install.html). Following the Livebook [Escript README section](https://github.com/elixir-nx/livebook#escript), this is how to install Livebook. ``` mix do local.rebar --force, local.hex --force mix escript.install hex livebook ``` Then locally launching Livebook is: ```bash theme={null} livebook server ``` When connecting to a Fly.io app, we can pass the needed ENV value like this: ```bash theme={null} LIVEBOOK_DISTRIBUTION=name ERL_AFLAGS="-proto_dist inet6_tcp" livebook server ``` > [!NOTE] > > **Upgrading an Escript Installed Livebook** > > When upgrading your Livebook version while using [asdf](https://asdf-vm.com/) for managing your Elixir and Erlang installs, then here's a helpful tip: > > * Remove the existing `livebook` shim: `rm ~/.asdf/shims/livebook` > * Install the new version: `mix escript.install hex livebook` > * Re-shim to get the command: `asdf reshim elixir` > * Verify you have the upgrade: `livebook --version` ### ENV Values The `ERL_AFLAGS` settings is passed to the BEAM on startup. The `LIVEBOOK_DISTRIBUTION=name` setting configures Livebook to run with a fully qualified name and to be ready for clustering. The Livebook README page [documents the supported ENV values](https://github.com/livebook-dev/livebook#environment-variables). ## Running Livebook Once started, Livebook generates a link in the console you use to connect to it. The Livebook link in the console looks something like this: ``` [Livebook] Application running at http://localhost:8080/?token=uzbwvvpexj7mdftwjilezi4qaygt2bfc ``` With Livebook running locally, let's get started! ### Create or Load a Notebook Let's create a new notebook to test the remote connection. Create a new notebook titled, "Remote Connect Test" and add a Section. On the left side, click the "Runtime settings" as pictured below: ![Runtime settings location on sidebar](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-data-analysis-runtime-click-step1.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=6e05c280abdd3327e0baed3959ec5e01) Before we can run any Elixir code, we need to connect to the remote server. To connect to the remote server, we will "Configure" the server we want to connect to. ![Runtime settings configure connection](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-data-analysis-runtime-click-step2.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=6421c26fab85a287ca84721821b2ca69) Now we use "Attached Node" to connect to an existing Elixir node. ![Runtime settings configure connection](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-data-analysis-runtime-click-step3.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=ee30230e10e637ebfced2253ca04cdbf) With the "Attached Node" mode selected, we need to provide 2 additional pieces of information before we can connect. ![Attach steps](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-runtime-attach-steps.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=19be944a2a2e619ebfd267641c6bc03c) 1. **Name** - Our node's name 2. **Cookie** - The cookie value used by the deployed server ### Getting the Node Name Let's see how to get the node name and private IP address needed. To list your deployed apps: ```bash cmd theme={null} fly apps list ``` ```output output theme={null} ... icy-leaf-7381 personal running 49m26s ago ``` Get the private IPv6 address for the desired app. ```bash cmd theme={null} fly ips private --app icy-leaf-7381 ``` ```output output theme={null} ID REGION IP 44040812 lax fdaa:0:1da8:a7b:bf88:71ca:52:2 ``` Combine the app name with the private IPv6 address to get the full node name. For our example here, it would be: ``` icy-leaf-7381@fdaa:0:1da8:a7b:bf88:71ca:52:2 ``` ### Using the Cookie We also need the cookie used by the server. See [this guide](/elixir/the-basics/clustering) for setting that up for your app. ### Attaching to a Fly Server **REMEMBER:** At this point, make sure your WireGuard connection to Fly is up! When you have the name and cookie, enter those and "**Connect**" to the server. **REMEMBER:** Each time we deploy our apps, the private IP will change. We need to get the current IP before we can connect again. Once connected, we have code completion available in the Elixir cells for the app we are connected to. The [HelloElixir app](https://github.com/fly-apps/hello_elixir) doesn't have anything useful to run so we can just prove to ourselves that our code is being executed remotely. Add the following code to a Livebook Elixir cell and execute it. ```elixir theme={null} require Logger Logger.info("Testing logger") ``` Now, check the logs of the production app. You should find your logger output there! ```bash cmd theme={null} fly logs ``` ```output output theme={null} ... 2021-06-21T22:47:21.415426797Z app[44040812] sjc [info] 22:47:21.414 [info] Testing logger ``` ### Success! We successfully executed Elixir code through Livebook connected to a remote server running on Fly.io! The code was actually executed on the server! Looking for things to do now that you're connected? Check out: [Do Interesting Things with Livebook and Your Production App](/elixir/advanced-guides/interesting-things-with-livebook) ## Troubleshooting Checklist If you have problems connecting, here are a few things to check: * Ensure your WireGuard connection is up. * Check that the version of Elixir on the server is 1.14.2 or higher. * Check that the version of Elixir declared in the project's Dockerfile. * Check that the version of Elixir on the client matches the server. * If you re-deployed, you need to get the new IP address for the deployed app. * Check the server logs. Failed connections may still log errors at the server. * Check the local livebook logs. Failed connections may log helpful messages. # Connecting Observer to Your App in Production Source: https://docs.fly.io/elixir/advanced-guides/connect-observer-to-your-app Run Erlang Observer locally and connect it to a production Elixir app on Fly.io over a WireGuard tunnel to inspect and kill processes, with a helper script. Elixir, Erlang, and really just the BEAM has a slick feature called "[Observer](https://elixir-lang.org/getting-started/debugging.html#observer)". It's a powerful UI that connects to a running Elixir or Erlang node and let's you "observe" what's going on inside. It has some limited ability to modify things as well, most notably you can kill running processes. This can help when something is misbehaving or you just want to play "chaos monkey" and kill parts of the system to see how it recovers. This shows a process tree for the application. Using this I can inspect individual processes or even kill them! ![Observer screen shot](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/observer-tictac-local-application-pane.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=db41d13e2491d3c3851504d47014eb61) One very cool way to run Observer is to run it on your local machine (which has the ability to display the UI) and connect to a production server (with no windowing UI available) and "observe" it from a distance. So yeah... have a problem in production? Not sure what's going on? You can literally tunnel in, crack the lid and poke, prod, and peek around to see what's going on. Next we'll cover how your project can support this feature and see how to do it on Fly.io! ## What We Will Do Fly.io natively supports [WireGuard](https://www.wireguard.com/), Jason Donenfeld's amazing VPN protocol. If you’ve ever lost hours of your life trying to set up an IPSec VPN, you’ll be blown away by how easy WireGuard is. It’s so flexible and performant that Fly uses it as our network fabric. And it’s supported on [every major platform](https://www.wireguard.com/install/), including macOS, iOS, Windows, and Linux. What that means for you is that if your app runs on Fly, you can open a secure, private, direct connection from your dev machine to your production network, in less time than it took me to write this paragraph. Cool, right? This is what we're going to do. ![WireGuard observer connection](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/elixir-wireguard-observer-tunnel.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=97a607ebf8c968b58eb7ed801d385678) We will bring up a secure WireGuard tunnel that links to your servers on Fly. In this graphic, there are two `my_app` Elixir nodes clustered together running on Fly. From the local machine, we can open an IEx terminal configured to **join** that cluster of remote Elixir nodes. Our local machine supports running Observer and drawing the UI. We use our local observer to talk to the remote nodes in the cluster! Let's do it. This will be fun! ## Sharing a Cookie The deployed servers running on Fly need a predictable, stable, known cookie value used for allowing nodes to join each other to create a cluster. This is required for your local node to be able to connect to the remote nodes. Your local node needs the cookie value too! See [this guide on creating a static cookie value](/elixir/the-basics/clustering#the-cookie-situation) in your Elixir project. ## WireGuard Tunnel Setup WireGuard on your local machine. Follow the Fly.io [Private Network VPN](/networking/private-networking#private-network-vpn) guide to walk through that. ## Connecting to Production To make the connection, there are several steps in the process. We'll create a short script to automate the process for us! ### Knowing the Cookie Our script needs to know the cookie value. The easiest way to do this and keep the script generic is to set the value in the ENV. This lets us copy the script unchanged to multiple projects. To help manage project-specific ENV values, I like using [direnv](https://direnv.net/). When changing into a directory with an `.envrc` file, it loads those values into my ENV, when I leave that directory, it unloads them. This means I can set a COOKIE value (or other config) specific to each project and it works great. You don't have to use a tool like [`direnv`](https://direnv.net/) though. The `./observer` script file can be customized to set the COOKIE value explicitly if you prefer that approach. Refer to the full [script file here](https://github.com/fly-apps/hello_elixir/blob/main/observer#L14) in the comments to see how you can do that. ### Script File This is a bash script to kick off a correctly configured local IEx session, connect a new local node to the remote cluster, and start Observer. Here's a copy of the **[`observer`](https://github.com/fly-apps/hello_elixir/blob/main/observer) script file**. This should work fine on Linux and MacOS. On Windows, if you are using [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) then it will work because it's Linux. Otherwise, refer to the manual steps outlined below. After creating the file locally, make the script file executable: ```bash theme={null} chmod +x observer ``` Execute the script: ```bash cmd theme={null} ./observer ``` ```output output theme={null} Attempting to connect to hello-elixir-01HR8CXEYKQ9RXYQFEWSE5PTE0@fdaa:0:1da8:a7b:a160:73d2:f48b:2 Erlang/OTP 26 [erts-14.2.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit] Node Connected?: true Connected Nodes: [:"hello-elixir-01HR8CXEYKQ9RXYQFEWSE5PTE0@fdaa:0:1da8:a7b:a160:73d2:f48b:2"] ... Interactive Elixir (1.16.1) - press Ctrl+C to exit (type h() ENTER for help) ``` When observer first opens, it might looks something like this: ![Observer connected to local node](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/observer-local-node.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=6928ff9b7d4653aea2beca2d45a94c77) Notice that the window title shows `my_remote@...`? This means it's showing the stats of my local IEx node that isn't actually running any of my code. So this data isn't very interesting yet. If everything worked and it's connected, under the Nodes menu you should see the connected remote node. ![Observer connected to local node](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/observer-local-node-menu.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=b9ccb291d4fea966429bc16113459203) When the remote node is selected, then all the stats and information changes to reflect what's going on in the selected node. ![Observer connected to local node](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/observer-local-node-connected.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=91fa6c651187929ad6e60d3c7cefe1d1) It worked! I'm seeing the information for the production node! ### Success! Let's review briefly what was accomplished. * I setup a WireGuard tunnel from my personal computer into my private Fly network. * I started a local Elixir node that shares the same cookie value. * My local node connected over WireGuard to the production cluster. * I launched Observer. Now, using WireGuard and this script, I can easily launch Observer and observe any node in the cluster! ## Disconnecting When done, close Observer. It leaves you with an open IEx shell that is still connected to the remote cluster. You can safely CTRL+C, CTRL+C to exit it. At this point you can shutdown your WireGuard connection as well if desired. ## Tips and Troubleshooting The script is a simple tool to make it easy to launch observer and connect to the cluster. It doesn't diagnose or handle all the things that can fail. For instance, if your WireGuard connection isn't up, it just won't find the server but it also won't complain. If you encounter issues, you can go through the manual steps below to help diagnose any problems. In order for everything to work, here's the checklist overview: * Your wireguard connection must be up. * Your application defines a release that specifies the cookie value to use. * The local COOKIE value must be the same as the cookie value used in production. * Observer needs to be working in your local environment. That requires WxWidget support in your Erlang install. ### Networking and the BEAM Fly.io uses an IPv6 network internally for private IPs. The BEAM needs IPv6 support to be enabled explicitly. That's taken care of for the server through the Dockerfile. Locally, however, it needs to be enabled as well so the machine running Observer can actually *connect* to the remote node. The issue is, if IPv6 support is enabled globally, like in a `.bashrc` file, then setting it in the `observer` script essentially flips it OFF. If NOT set globally, then it should be set in the script. Choose the version that fits your situation by modifying the script. It's the `--erl "-proto_dist inet6_tcp"` portion. Example: ``` iex --erl "-proto_dist inet6_tcp" --sname my_remote --cookie ${COOKIE} -e "IO.inspect(Node.connect(:'${FULL_NODE_NAME}'), label: \"Node Connected?\"); IO.inspect(Node.list(), label: \"Connected Nodes\"); :observer.start" ``` Versus without the `--erl` option: ``` iex --sname my_remote --cookie ${COOKIE} -e "IO.inspect(Node.connect(:'${FULL_NODE_NAME}'), label: \"Node Connected?\"); IO.inspect(Node.list(), label: \"Connected Nodes\"); :observer.start" ``` ### Manual Script Steps If you encounter issues, this can help you diagnose what's going on. The script automates 4 things. 1. Getting the cookie value from the ENV - make sure the correct cookie value is either available in the ENV or explicitly set in the script. 2. Uses the `fly status` command to get the app name, Docker image ref, and the first private IP. This is used to build the fully qualified node name. You only need one IP address. Once you join to any node you are introduced and connected to all of them. 3. Set up a local node and executes multiple commands. 4. It runs a command like `Node.connect(:'APP_NAME-IMAGE_REF@IPv6_ADDRESS')` to connect to the remote node. It returns `true` when it succeeds or `false` when it fails. The app name and the IP address used to make up the node's name are assembled from the previous steps. 5. Launch observer with the command `:observer.start`. If this fails, check the other tip for WxWidgets. To do it manually, once you get the IP address, you can customize the following command to launch Observer. ``` iex --erl "-proto_dist inet6_tcp" --sname my_remote --cookie ${YOUR-COOKIE-VALUE} -e "IO.inspect(Node.connect(:'${FULL_NODE_NAME}'), label: \"Node Connected?\"); :observer.start" ``` You need to substitute in your `YOUR-COOKIE-VALUE` value, the `FULL_NODE_NAME`. ### WxWidgets Support If you are using [asdf-vm](https://asdf-vm.com/) for managing your Elixir and Erlang versions, check out the [Erlang plugin's documentation](https://github.com/asdf-vm/asdf-erlang) for getting WxWidget support in your Erlang environment. This is required for using Observer. To test if WxWidgets and observer is working correctly on you local machine, try the following. Start IEx: ``` iex ``` Start observer: ``` :observer.start ``` It should bring Observer running on your machine. It won't be connected to anything else, but it verifies that the libraries and dependencies are working. # GitHub Actions for Elixir CI/CD Source: https://docs.fly.io/elixir/advanced-guides/github-actions-elixir-ci-cd Set up a GitHub Actions CI workflow for an Elixir project with a Postgres service, dependency caching, and retries, then auto-deploy it to Fly.io. You want the benefits of modern CI/CD for your Elixir projects? You're in the right place! ## Continuous Integration (CI) Continuous integration is a software development practice where developers frequently merge code changes into a central repository. Automated builds and tests are run to assert the new code's correctness before integrating the changes into the main development branch. The goal is to find and correct bugs faster, improve software quality and enable software releases to happen faster. To get started with GitHub Actions in your project, let's create a "test" workflow. To do this, create this path and file in the root of your project: `.github/workflows/elixir.yaml` Let's look at a sample file. Comments are included to explain and document what we're doing and why. ```yaml theme={null} name: Elixir CI # Define workflow that runs when changes are pushed to the # `main` branch or pushed to a PR branch that targets the `main` # branch. Change the branch name if your project uses a # different name for the main branch like "master" or "production". on: push: branches: [ "main" ] # adapt branch for project pull_request: branches: [ "main" ] # adapt branch for project # Sets the ENV `MIX_ENV` to `test` for running tests env: MIX_ENV: test permissions: contents: read jobs: test: # Set up a Postgres DB service. By default, Phoenix applications # use Postgres. This creates a database for running tests. # Additional services can be defined here if required. services: db: image: postgres:12 ports: ['5432:5432'] env: POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 runs-on: ubuntu-latest name: Test on OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} strategy: # Specify the OTP and Elixir versions to use when building # and running the workflow steps. matrix: otp: ['25.0.4'] # Define the OTP version [required] elixir: ['1.14.1'] # Define the elixir version [required] steps: # Step: Setup Elixir + Erlang image as the base. - name: Set up Elixir uses: erlef/setup-beam@v1 with: otp-version: ${{matrix.otp}} elixir-version: ${{matrix.elixir}} # Step: Check out the code. - name: Checkout code uses: actions/checkout@v3 # Step: Define how to cache deps. Restores existing cache if present. - name: Cache deps id: cache-deps uses: actions/cache@v3 env: cache-name: cache-elixir-deps with: path: deps key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} restore-keys: | ${{ runner.os }}-mix-${{ env.cache-name }}- # Step: Define how to cache the `_build` directory. After the first run, # this speeds up tests runs a lot. This includes not re-compiling our # project's downloaded deps every run. - name: Cache compiled build id: cache-build uses: actions/cache@v3 env: cache-name: cache-compiled-build with: path: _build key: ${{ runner.os }}-mix-${{ env.cache-name }}-${{ hashFiles('**/mix.lock') }} restore-keys: | ${{ runner.os }}-mix-${{ env.cache-name }}- ${{ runner.os }}-mix- # Step: Conditionally bust the cache when job is re-run. # Sometimes, we may have issues with incremental builds that are fixed by # doing a full recompile. In order to not waste dev time on such trivial # issues (while also reaping the time savings of incremental builds for # *most* day-to-day development), force a full recompile only on builds # that are retried. - name: Clean to rule out incremental build as a source of flakiness if: github.run_attempt != '1' run: | mix deps.clean --all mix clean shell: sh # Step: Download project dependencies. If unchanged, uses # the cached version. - name: Install dependencies run: mix deps.get # Step: Compile the project treating any warnings as errors. # Customize this step if a different behavior is desired. - name: Compiles without warnings run: mix compile --warnings-as-errors # Step: Check that the checked in code has already been formatted. # This step fails if something was found unformatted. # Customize this step as desired. - name: Check Formatting run: mix format --check-formatted # Step: Execute the tests. - name: Run tests run: mix test ``` ### When the Workflow Runs... When code is pushed to the `main` branch, this workflow is run. This happens either from directly pushing to the `main` branch or after merging a PR into the `main` branch. This workflow is also configured to run checks on PR branches that target the `main` branch. This is where it is most helpful. We can work on a fix or a new feature in a branch and as we work and push our code up, it automatically runs the full gamut of checks we want. When all steps in the workflow succeed, the workflow "passes" and the automated checks say it can be merged. When a step fails, the workflow halts at that point and "fails", potentially blocking a merge. ### Customizing the Workflow Steps This workflow is a starting point for a team. Every project is unique and every team values different things. Is this missing something your team wants? Check out some additional steps that can be added to your workflow. * Add a step to run [Credo](https://github.com/rrrene/credo) checks. * Add a step to run [Sobelow](https://github.com/nccgroup/sobelow) for security focused static analysis. * Add a step to run [dialyxir](https://github.com/jeremyjh/dialyxir). This runs [Dialyzer](https://www.erlang.org/doc/man/dialyzer.html) static code analysis on the project. Refer to the project for tips on caching the PLT. * Customize the `mix test` command to include [code coverage](https://hexdocs.pm/mix/main/Mix.Tasks.Test.html#module-coverage) checks. * Add Node setup and caching if `npm` assets are part of the project's test suite. * Add a step to run [mix\_audit](https://github.com/mirego/mix_audit). This provides a `mix deps.audit` task to scan a project's Mix dependencies for known Elixir security vulnerabilities * Add a step to run [`mix hex.audit`](https://hexdocs.pm/hex/Mix.Tasks.Hex.Audit.html). This shows all Hex dependencies that have been marked as retired, which the package maintainers no longer recommended using. * Add a set to run [`mix deps.unlock --check-unused`](https://hexdocs.pm/mix/Mix.Tasks.Deps.Unlock.html). This checks that the `mix.lock` file has no unused dependencies. This is useful if you want to reject contributions with extra dependencies. ### Benefits of Caching It's worth spending time tweaking your caches. Why? A lot of effort has been put into speeding up Elixir build times. If we don't cache the build artifacts, then we don't reap any of those benefits! Of course, faster build times means you spend less money running your CI workflow. But that's not the reason to do it! Better caches mean the checks are performed faster and that means faster feedback. Faster feedback means that, as a team, you save time and can move faster. No waiting 20 minutes for the checks to complete so a PR can be merged. (Yes, I have felt that pain!) This starting template builds in two caching steps. If `node_modules` factor into your project's tests, then caching there makes a lot of sense too. Just keep in mind that the reason we cache is to reduce drag on the speed of our team. If our caches ever cause a problem, and sometimes they can, it's good to know how to clear them. In our project, go to Actions > (Sidebar) Management > Caches. This is the list of caches saved for the project. We can use our naming format to identify which cache file is for what. ### Retrying Failed Builds We cache aggressively because we want the productivity benefits that incremental compiles bring the team. However, on occasion, incremental builds can fail. The quick fix is to perform a full recompile. The following step from our CI pipeline does this for us. ```yaml theme={null} # Step: Conditionally bust the cache when job is re-run. # Sometimes, we may have issues with incremental builds that are fixed by # doing a full recompile. In order to not waste dev time on such trivial # issues (while also reaping the time savings of incremental builds for # *most* day-to-day development), force a full recompile only on builds # that are retried. - name: Clean to rule out incremental build as a source of flakiness if: github.run_attempt != '1' run: | mix deps.clean --all mix clean shell: sh ``` If this is not the first run attempt, meaning we are re-running the job, then it busts our cache by cleaning out the `deps` and `_build` directories. This compromise works well. Most of the time, we get fast checks and, where there is a problem, we only need to retry the failed task to force a clean build. ### Additional Resources * [Documentation: Caching dependencies to speed up workflows](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) * [Managing Caches](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#managing-caches) * [erlef/setup-beam](https://github.com/erlef/setup-beam) - Set up your BEAM-based GitHub Actions workflow (Erlang, Elixir, and more) * See project for settings and ENV options * [Elixir setup action - Archived](https://github.com/actions/setup-elixir#phoenix-example) - Includes helpful examples like setting up the Postgres service database. * [Felt blog: Taking Hashrocket's "Ultimate Elixir CI" to the Next Level](https://felt.com/blog/hashrocket-ultimate-elixir-to-the-next-level) * [felt/ultimate-elixir-ci](https://github.com/felt/ultimate-elixir-ci) - Good examples on CI setup with explanations. * [Source for cleaning out incremental builds on retry](https://github.com/felt/ultimate-elixir-ci/blob/main/.github/actions/elixir-setup/action.yml#L97) ## Continuous Deployment (CD) Continuous deployment is a software development practice that automatically builds and releases the project into production when new changes pass automated checks and are merged to the main branch. Using this practice, it is common to release new versions of the applications multiple times a day, ideally, without users even being aware of the process. ### Auto-Deploying our Elixir Applications We can easily auto-deploy our Elixir applications on Fly.io. There is nothing specific to Elixir about this process, so please refer to this guide for the details. [Continuous Deployment with Fly.io and GitHub Actions](/launch/continuous-deployment-with-github-actions) # Explore Your Production App with Livebook Source: https://docs.fly.io/elixir/advanced-guides/interesting-things-with-livebook Use Livebook connected to your Elixir app on Fly.io for business intelligence, querying production data and charting it with VegaLite and Kino. [Livebook](https://livebook.dev/) is an exciting advancement in the Elixir community. It was created for the Elixir machine learning library [nx](https://github.com/elixir-nx/nx). You can think of it as Elixir's version of [Jupyter Notebooks](https://jupyter.org/). Livebook ends up being very flexible and powerful. Because Elixir nodes can easily be clustered together, you can run Livebook on your local machine, connect it to your Elixir servers, and do some really interesting things. Once you have [Livebook connected to your app on Fly](/elixir/advanced-guides/connect-livebook-to-your-app), you are ready to start playing! ## Getting Started with Livebook Once you have Livebook running locally, **visit the "Learn" section** for examples of things you can do. Also, check out the [Livebook News](https://news.livebook.dev/) to find interesting developments and to spark new ideas. Here are a few ideas of things you can do when Livebook is connected to your server. * Query your users grouping by an interesting attribute and graph it. * Chart user sign-ups over time. * Analyze customer orders in a table. * Create scripts in the form of notebooks to run common administrative commands. ## Business Intelligence Through Livebook Let's walk through using Livebook to get Business Intelligence (BI) data from your Elixir app. ### Visualization Setup With [Livebook v0.2](https://github.com/livebook-dev/livebook/blob/main/CHANGELOG.md), support was added for visualizations through the Elixir packages [VegaLite](https://github.com/livebook-dev/vega_lite) and [Kino](https://github.com/livebook-dev/kino). In this example, we're connecting a locally running Livebook to our app running on Fly. In order for the visualization tools to work, these libraries need to be installed on our server. You may have seen Livebook examples showing how we can `Mix.install` the needed graphics libraries. That doesn't work when you are attaching to a remote mix project. It's actually a good thing that we can't just `Mix.install` like that on a remote project. You wouldn't want anyone with access to attach to your live system and install random packages! That just means we need to install the dependencies first. Add the `vega_lite` and `kino` libraries to your project's `mix.exs` file and deploy your app. ```elixir theme={null} defp deps do [ {:phoenix, "~> 1.5.6"}, # ... # Graphing/charting - for Livebook or analysis {:vega_lite, "~> 0.1.0"}, {:kino, "~> 0.1.0"} ] end ``` With the updated app deployed to Fly, Livebook can connect and render charts and visualizations! ### Starting Your Analysis Notebook While you are figuring out **what** you want to examine in your app, you can explore it locally on your dev machine. Once you start, you may find you want to update your project code. Also, while playing locally, you reduce the chance of a badly structured query negatively impacting your live system. I like [starting Livebook using the EScript method](https://github.com/livebook-dev/livebook#escript). Once installed, in a terminal you just go to the directory where your project's source code lives and start Livebook from there. ```bash theme={null} livebook server ``` #### Create a Notebook Open Livebook in a browser and create a "New notebook". For the first notebook, let's keep it really generic because I don't know what makes sense for your project. Name the notebook something like "Exploring Data". Create a section for "Setup" and one for "Analysis". Save the notebook with your project. I suggest creating a "notebook" folder to hold them so they can be checked in with your project and shared with the team. #### Connect to Your Project At this point, we haven't connected Livebook to our project so we can't access the project's code yet. To do this, start the project with something similar to the following command: ``` iex --sname my_app --cookie secret -S mix phx.server ``` This starts our local project with a short name of "my\_app" and a cookie of "secret". Back in Livebook, click the "Runtime settings" button. ![Livebook runtime connection](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-runtime-link.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=bb535a796296950f56a5dd5eba696f3a) From here, click "Configure" then "Attached Node". Now, enter the name "my\_app" and the cookie "secret" (or whatever values were used when starting the server) and click "Connect". If everything went well, Livebook is connected to your locally running application! We can verify it's connected by typing a module from the project into a code cell in the notebook and getting auto-complete help that's specific to our project. #### Your First Analysis This is where everything gets specific to the project. I'll walk through an example so we can see how to pull data, aggregate it, and visualize it. Then we'll turn you loose on your project! My project contains survey-style questions. Some questions use a "personality" type field. My first goal is just to get the data and some counts to see how it's distributed. Under **Setup**, I add an Elixir cell and the following code: ```elixir theme={null} alias VegaLite, as: Vl alias Core.Schemas.Question alias Core.Repo import Ecto.Query ``` The important one for visualizations is: ```elixir theme={null} alias VegaLite, as: Vl ``` My first query groups the questions by the personality type value and returns the count. ```elixir theme={null} query = from(q in Question, group_by: q.personality, select: {q.personality, count(q.id)} ) personality_counts = query |> Repo.all() |> Enum.into(%{}) ``` The results look like this: ```elixir theme={null} %{any: 77, extrovert: 10, introvert: 13} ``` That's helpful already, but it's still just data. I'd like to see what that looks like. I add another Elixir cell and write the following code: ```elixir theme={null} # Convert the raw data into a Pie Chart friendly format data = Enum.map(personality_counts, fn {type, count} -> %{"personality" => Atom.to_string(type), "value" => count} end) Vl.new() |> Vl.data_from_values(data) |> Vl.mark(:arc) |> Vl.encode_field(:theta, "value", type: :quantitative) |> Vl.encode_field(:color, "personality", type: :nominal) |> Vl.config(view: [stroke: nil]) ``` The `Vl` is the VegaLite alias. Here's the result: ![Pie chart visualization](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-personality-pie-visualization.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=5d66e4b2ec598de374153d45f372f2c8) Nice! I get a better sense for the personality type distribution in the question data. What if I want to render the data as bar charts? ```elixir theme={null} # Convert the raw data into a Bar Chart friendly format data = Enum.map(personality_counts, fn {type, count} -> %{"Personality" => Atom.to_string(type), "Question Count" => count} end) # Sort the data by counts to order the results data = Enum.sort_by(data, & &1["Question Count"], :desc) Vl.new(width: 300, height: 300) |> Vl.data_from_values(data) |> Vl.mark(:bar) |> Vl.encode_field(:x, "Personality", type: :nominal, axis: [label_angle: 0], sort: [field: "Question Count", order: "descending"] ) |> Vl.encode_field(:y, "Question Count", type: :quantitative) ``` Here's the result: ![Bar chart visualization](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-personality-bar-visualization.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=2d7a09ac2c2d6d16cc48a90563585559) ### Getting Started with VegaLite If the VegaLite code looks scary, don't worry! I'm actually a total VegaLite noob! Livebook includes some built-in notebooks you should check out for code examples on how to structure data and configure VegaLite for the visualization you want. ![VegaLite examples](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/livebook-vegalite-examples.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=c228f038bd7318b21070f2eabde4f698) Then I customized a little using the [VegaLite docs](https://vega.github.io/vega-lite/docs/) and [examples](https://vega.github.io/vega-lite/examples/). ### Summary Livebook is an awesome tool for BI on your Elixir applications. Because it's going directly to your **code** and not just your **data**, you can use your code to query integrated services like Stripe and do awesome things! # Elixir: SQLite3 Source: https://docs.fly.io/elixir/advanced-guides/sqlite3 Run a Phoenix app with SQLite3 on Fly.io by storing the database on a volume, running migrations on startup, and copying data over from Postgres or MySQL. While Elixir 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). This guide will assume you have setup and configured Phoenix Application using [ecto\_sqlite3](https://github.com/elixir-sqlite/ecto_sqlite3) running locally. To make this work, you will need to place your databases on persistent [Volumes](https://www.sqlite.org/index.html) 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 Elixir applications that use SQLite3 for their database can't be deployed to multiple regions. But if you are okay using beta software, [LiteFS](/litefs) could work for multi-region sync, check it out! But this guide is going to assume you have one node and one volume. This guide is heavily influenced by the [guide from](https://gist.github.com/mcrumm/98059439c673be7e0484589162a54a01) Phoenix Core Team Member, Michael Crumm. Following are the steps required to make this work: ## Create volume ```bash theme={null} fly volumes create name ``` Replace `name` with your desired volume name. Only alphanumeric characters and underscores are allowed in names. Optionally, you may specify the size of the volume, in gigabytes, by adding a `--size int` argument. The default volume size is 3 gigabytes. ## Mount and Prep for Deployment Add the following to your `fly.toml`, once again replacing the name with what you selected, this time in two places: ```toml theme={null} [mounts] source="name" destination="/mnt/name" ``` Next remove the release step from the `[deploy]` section of your `fly.toml` and add a DATABASE\_PATH variable: ```diff theme={null} [env] + DATABASE_PATH = "/mnt/name/name.db" -[deploy] - release_command = "/app/bin/migrate" ``` This step is required because a volume may not be ready once your application release runs, so to fix this we need to run migrations on application start. This can be accomplished by adding the following line to your `lib/name/application.ex`: ```diff theme={null} @impl true def start(_type, _args) do + Name.Release.migrate() children = [ ``` ## Deploy These changes can be deployed: ```bash theme={null} fly deploy ``` ## Converting to SQLite3 Creating a new project with SQLite3 as the default is as simple as ```shell theme={null} mix phx.new my_app --database sqlite3 ``` But if you're working with existing code, here are the steps you'll need to make on a Phoenix 1.6+ application. Append the following your `.gitignore` to ignore SQLite database files: ```.gitignore theme={null} # Database files *.db *.db-* ``` Replace the `:postgrex` dep from your `mix.exs` with `ecto_sqlite`. ```elixir theme={null} def deps do [ {:ecto_sqlite3, "~> 0.9.1"}, # Latest version at time of publication. # deps.. ] end ``` Update the repo configuration in `config/dev.exs`: ```elixir theme={null} config :name, Name.Repo, database: Path.expand("../name_dev.db", Path.dirname(__ENV__.file)), pool_size: 5, show_sensitive_data_on_connection_error: true ``` Update the repo configuration in `config/test.exs`: ```elixir theme={null} config :name, Name.Repo, database: Path.expand("../name_test.db", Path.dirname(__ENV__.file)), pool_size: 5, pool: Ecto.Adapters.SQL.Sandbox ``` And replace the `DATABASE_URL` in `config/runtime.exs` for production: ```elixir theme={null} if config_env() == :prod do database_path = System.get_env("DATABASE_PATH") || raise """ environment variable DATABASE_PATH is missing. For example: /data/name/name.db """ config :name, Name.Repo, database: database_path, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "5") ``` Finally, update your Repo to use the SQLite adapter in `lib/name/repo.ex`: ```elixir theme={null} defmodule Name.Repo do use Ecto.Repo, otp_app: :name, adapter: Ecto.Adapters.SQLite3 end ``` ## SQLite3 Tips ### Limitations The `ecto_sqlite3` documentation includes a [good guide](https://hexdocs.pm/ecto_sqlite3/Ecto.Adapters.SQLite3.html#module-limitations-and-caveats) on the limits of using Ecto with SQLite3. We recommend at least skimming this before putting it into production. ### Transferring Data from Postgres/MySQL to SQLite3 > [!NOTE] > > This section is something to give you a starting point on how to get from X to SQLite. Make sure to back up your data and be vigilant, because this kind of thing is fraught at best. There is really no very easy way to do this since the data types between the databases are different... But here is one way that has worked for me in the past and might work for you! The Ruby [Sequel](https://sequel.jeremyevans.net/) project comes with a [command line tool for copying databases](https://sequel.jeremyevans.net/rdoc/files/doc/bin_sequel_rdoc.html#label-Copy+Databases). This method *should* help when transferring between ADO, Amalgalite, IBM\_DB, JDBC, MySQL, Mysql2, ODBC, Oracle, PostgreSQL, SQLAnywhere, and TinyTDS to SQLite3. That said it has limits! This is directly from the documentation: > [!NOTE] > > This copies the table structure, table data, indexes, and foreign keys from the MySQL database to the PostgreSQL database. > > Note that the support for copying is fairly limited. It doesn’t handle database views, functions, triggers, schemas, partial indexes, functional indexes, and many other things. Also, the data type conversion may not be exactly what you want. It is best designed for quick conversions and testing. For serious production use, use the database’s tools to copy databases for the same database type, and for different database types, use the Sequel API. So YMMV on how useful this is for you. If you have a relatively simple database it might work great! Install Ruby if you don't already have it on your machine. #### macOS ```bash theme={null} brew install ruby ``` or #### Ubuntu ```bash theme={null} apt-get install ruby-full ``` The Ruby database adapter Sequel comes with a really slick command line tool for copying databases; let's install that ```sh theme={null} gem install sequel ``` Depending on your database, you may need to install an extra adapter. In this example we use Postgres, so let's install that: ```bash theme={null} gem install pg ``` Next all we need to do is run the Sequel Copy command, where the first database is the source and the second database is the destination: ```bash theme={null} sequel -C postgres://localhost/database sqlite://name.db ``` And that's it! If you open up your SQLite database using the command line it should have your tables and data all moved over. If you get an error here about connecting you will need to figure out which ruby gem handles your adapter and install it like we did for pg. ```bash theme={null} sqlite3 name.db ``` #### Note on types SQLite doesn't have official support for Postgres Arrays or Hstores, and most special datatypes! These will be copied into strings in the resulting SQLite table, so for Arrays the data will look like `{item1, item2, item3}` if you want to still use this as an array you need to use [string manipulation](https://www.sqlitetutorial.net/sqlite-replace-function/) to convert them to json and then it should work just fine. This is just one example, but the positive is that it's all just strings in SQLite, so if you can make the string look like json you are set! ### Copying an existing database to a fly volume If you've exported or copied your database to SQLite you will need to get your database file up to Fly.io. To do this we will use the [fly sftp](/flyctl/cmd/fly_sftp) command. First open an sftp shell ```bash cmd theme={null} fly sftp shell ``` ```output output theme={null} » ``` And use the `put` command to transfer your file to the volume path. **NOTE**: Because our server is running, we first need to give the database a new name. Do **not** try to put this file in the same place as your current `DATABASE_PATH`. ``` » put ./name.db mnt/volume_name/name-prod.db ``` Where `volume_name` corresponds to the name you used when [creating your volume](#create-volume). Check that it's there: ```sh theme={null} » ls /mnt ``` ```sh theme={null} name.db name-prod.db .... other files ``` Then `ctrl-c` to exit. Finally, update your env in `fly.toml`: ```diff theme={null} [env] - DATABASE_PATH = "/mnt/name.db" + DATABASE_PATH = "/mnt/name-prod.db" ``` And to persist that change: ```bash theme={null} fly deploy ``` Next time it boots it should use your new database! ### Download a local copy of your production database You can use the [fly sftp get](/flyctl/cmd/fly_sftp_get) command to download your database locally. ```elixir theme={null} fly sftp get /mnt/name/name-prod.db prod.db ``` Replace `/mnt/name/name-prod.db` with the path to your database (DATABASE\_PATH). # Deploy an Existing Elixir App Source: https://docs.fly.io/elixir/getting-started/existing Move an existing Elixir or Phoenix app to Fly.io: launch with fly launch, attach Postgres, deploy, and troubleshoot your first release. If you have an existing Elixir app that you want to move over to Fly, this guide walks you through the initial deployment process and shows you techniques you can use to troubleshoot issues you may encounter in a new environment. ### Provision Elixir and Postgres Servers To configure and launch your Elixir app, you can use `fly launch` and follow the wizard. When you run `fly launch` from the newly-created project directory, the launcher provides some defaults for your new app, and gives you the option to tweak the settings. Run: ```sh theme={null} cd my-app-name fly launch ``` You'll get a summary of the defaults chosen for your app: ```output theme={null} Organization: MyOrgName (fly launch defaults to the personal org) Name: my-app-name (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes Opening https://fly.io/cli/launch/bea626e2d179a083a3ba622a367e24ec ... ``` Type `y` at the prompt to open the Fly Launch page, and make the following changes to your app config: * Change the default app name and region, if needed. * For Databases, select Fly Postgres, give the Postgres database app a name (for example, your app name with `-db` appended) and choose a configuration. Once you confirm your settings, you can return to the terminal, where the launcher will: * Run the deployment setup task * Build the image * Set secrets required by (`SECRET_KEY_BASE`, for example) * Deploy the application in your selected region ```output theme={null} Waiting for launch data... Done Created app 'my-app-name' in organization 'personal' Admin URL: https://fly.io/apps/my-app-name Hostname: my-app-name.fly.dev Set secrets on my-app-name: SECRET_KEY_BASE Creating postgres cluster in organization personal Creating app... Setting secrets on app my-app-name-db... Provisioning 1 of 1 machines with image flyio/postgres-flex:15.3@sha256:44b698752cf113110f2fa72443d7fe452b48228aafbb0d93045ef1e3282360a6 Waiting for machine to start... Machine 2865550c7e96d8 is created ==> Monitoring health checks Waiting for 2865550c7e96d8 to become healthy (started, 3/3) Postgres cluster my-app-name-db created Username: postgres Password: EChe3BrhCjsPQEI Hostname: my-app-name-db.internal Flycast: fdaa:2:45b:0:1::1d Proxy port: 5432 Postgres port: 5433 Connection string: postgres://postgres:EChe3BrhCjsPQEI@my-app-name-db.flycast:5432 Save your credentials in a secure place -- you won't be able to see them again! Connect to postgres Any app within the MyOrgName organization can connect to this Postgres using the above connection string Now that you've set up Postgres, here's what you need to understand: https://fly.io/docs/postgres/getting-started/what-you-should-know/ Checking for existing attachments Registering attachment Creating database Creating user Postgres cluster my-app-name-db is now attached to my-app-name The following secret was added to my-app-name: DATABASE_URL=postgres://aa_hello_elixir2:Er6pLzUBuhKcbBl@my-app-name-db.flycast:5432/aa_hello_elixir2?sslmode=disable Postgres cluster my-app-name-db is now attached to my-app-name Generating rel/env.sh.eex for distributed Elixir support Preparing system for Elixir builds Installing application dependencies Running Docker release generator Wrote config file fly.toml Validating /Users/anderson/test-elixir-gs/hello_elixir2/fly.toml ✓ Configuration is valid ==> Building image Remote builder fly-builder-black-pine-7645 ready Remote builder fly-builder-black-pine-7645 ready ==> Building image with Docker --> docker host: 20.10.12 linux x86_64 ... --> Pushing image done image: registry.fly.io/my-app-name:deployment-01HPMGHTG8XSYH3ZCV82SF5CEZ image size: 126 MB Watch your deployment at https://fly.io/apps/my-app-name/monitoring Provisioning ips for my-app-name Dedicated ipv6: 2a09:8280:1::2a:bc0b:0 Shared ipv4: 66.241.124.79 Add a dedicated ipv4 with: fly ips allocate-v4 Running my-app-name release_command: /app/bin/migrate ------- ✔ release_command 784eee4c294298 completed successfully ------- This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Checking DNS configuration for my-app-name.fly.dev Visit your newly deployed app at https://my-app-name.fly.dev/ ``` > [!NOTE] > > Make sure to note your Postgres credentials from the output. That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * `fly logs` - Tail your application logs * `fly status` - App deployment details * `fly status -a postgres-database-app-name` - Database deployment details * `fly deploy` - Deploy the application after making changes ## Troubleshooting your initial deployment Since this is an existing Elixir app, its highly likely it might not boot because you probably need to configure secrets or other service dependencies. Let's walk through how to troubleshoot these issues so you can get your app running. ### View log files If your application didn't boot on the first deploy, run `fly logs` to see what's going on. ```bash theme={null} fly logs ``` This shows the past few log file entries and tails your production log files. ### Open an IEx Session It can be helpful to open a IEx Session to run commands and diagnose production issues. To do this, we will [login with SSH](/flyctl/cmd/fly_ssh) to our application VM. There is a one-time setup task for using SSH. Follow the instructions. ``` fly ssh issue --agent ``` With SSH configured, let's open a console. ```bash cmd theme={null} fly ssh console --pty -C "/app/bin/hello_elixir remote" ``` ```output output theme={null} Connecting to hello_elixir.internal... complete Erlang/OTP 23 [erts-11.2.1] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help) iex(hello_elixir@fdaa:0:1da8:a7b:ac4:b204:7e29:2)1> ``` You have a live IEx shell into your application! ## Deploy your application after changes Deploying your application is done with the following command: ```bash theme={null} fly deploy ``` This will take a few seconds as it uploads your application, builds a machine image, deploys the images, and then monitors to ensure it starts successfully. Once complete visit your app with the following command: ```bash theme={null} fly apps open ``` If all went well, you'll see your Elixir application homepage. # Deploy a Phoenix App Source: https://docs.fly.io/elixir/getting-started/index Deploy an Elixir Phoenix app to Fly.io with Postgres. Generate the app, configure it with fly launch, and ship it with fly deploy. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/elixir-intro.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=86b52a16965f90e709e0dcffdd671dee) In this guide we’ll learn how to deploy an [Elixir Phoenix](https://www.phoenixframework.org/) application on Fly.io and connect it to a PostgreSQL database. > [!NOTE] > > **NOTE:** This guide is for apps generated on Phoenix 1.6.3 or later, where deployment is streamlined significantly. > If you're on an earlier version and can't upgrade, check out our **[legacy deployment guide](/elixir/getting-started/legacy)**. We'll be using the standard web application generated by the Elixir [Phoenix Framework](https://www.phoenixframework.org/). ## *Preparation* If you don't already have Elixir and Phoenix installed, get them set up before starting this guide. When you [install Elixir](https://elixir-lang.org/install.html), the [Mix](https://hexdocs.pm/mix/Mix.html) build tool ships with it. You can [install](https://hexdocs.pm/phoenix/installation.html#phoenix) the Phoenix project generator `phx.new` from a [Hex](https://hex.pm/docs/usage) package as follows: ```bash theme={null} mix archive.install hex phx_new ``` ## *Generate the app and deploy with Postgres* If you just want to see how Fly.io deployment works, this is the section to focus on. Here we'll bootstrap the app and deploy it with a Postgres database. First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. Now let's generate a shiny, new Phoenix app: ```bash theme={null} mix phx.new hello_elixir ``` The output ends with instructions for configuring the database and starting the app manually. The Fly.io launcher is going to take care of this, so we can ignore them. When you run `fly launch` from the newly-created project directory, the launcher provides some defaults for your new app, and gives you the option to tweak the settings. Run: ```sh theme={null} cd hello_elixir fly launch ``` You'll get a summary of the defaults chosen for your app: ```output theme={null} Organization: MyOrgName (fly launch defaults to the personal org) Name: hello_elixir (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes Opening https://fly.io/cli/launch/bea626e2d179a083a3ba622a367e24ec ... ``` Type `y` at the prompt to open the Fly Launch page, and make the following changes to your app config: * Change the default app name and region, if needed. * For Databases, select Fly Postgres, give the Postgres database app a name (for example, your app name with `-db` appended) and choose a configuration. Once you confirm your settings, you can return to the terminal, where the launcher will: * Run the deployment setup task * Build the image * Set secrets required by (`SECRET_KEY_BASE`, for example) * Deploy the application in your selected region ```output theme={null} Waiting for launch data... Done Created app 'hello_elixir' in organization 'personal' Admin URL: https://fly.io/apps/hello_elixir Hostname: hello_elixir.fly.dev Set secrets on hello_elixir: SECRET_KEY_BASE Creating postgres cluster in organization personal Creating app... Setting secrets on app hello_elixir-db... Provisioning 1 of 1 machines with image flyio/postgres-flex:15.3@sha256:44b698752cf113110f2fa72443d7fe452b48228aafbb0d93045ef1e3282360a6 Waiting for machine to start... Machine 2865550c7e96d8 is created ==> Monitoring health checks Waiting for 2865550c7e96d8 to become healthy (started, 3/3) Postgres cluster hello_elixir-db created Username: postgres Password: EChe3BrhCjsPQEI Hostname: hello_elixir-db.internal Flycast: fdaa:2:45b:0:1::1d Proxy port: 5432 Postgres port: 5433 Connection string: postgres://postgres:EChe3BrhCjsPQEI@hello_elixir-db.flycast:5432 Save your credentials in a secure place -- you won't be able to see them again! Connect to postgres Any app within the MyOrgName organization can connect to this Postgres using the above connection string Now that you've set up Postgres, here's what you need to understand: https://fly.io/docs/postgres/getting-started/what-you-should-know/ Checking for existing attachments Registering attachment Creating database Creating user Postgres cluster hello_elixir-db is now attached to hello_elixir The following secret was added to hello_elixir: DATABASE_URL=postgres://aa_hello_elixir2:Er6pLzUBuhKcbBl@hello_elixir-db.flycast:5432/aa_hello_elixir2?sslmode=disable Postgres cluster hello_elixir-db is now attached to hello_elixir Generating rel/env.sh.eex for distributed Elixir support Preparing system for Elixir builds Installing application dependencies Running Docker release generator Wrote config file fly.toml Validating /Users/anderson/test-elixir-gs/hello_elixir2/fly.toml ✓ Configuration is valid ==> Building image Remote builder fly-builder-black-pine-7645 ready Remote builder fly-builder-black-pine-7645 ready ==> Building image with Docker --> docker host: 20.10.12 linux x86_64 ... --> Pushing image done image: registry.fly.io/hello_elixir:deployment-01HPMGHTG8XSYH3ZCV82SF5CEZ image size: 126 MB Watch your deployment at https://fly.io/apps/hello_elixir/monitoring Provisioning ips for hello_elixir Dedicated ipv6: 2a09:8280:1::2a:bc0b:0 Shared ipv4: 66.241.124.79 Add a dedicated ipv4 with: fly ips allocate-v4 Running hello_elixir release_command: /app/bin/migrate ------- ✔ release_command 784eee4c294298 completed successfully ------- This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Checking DNS configuration for hello_elixir.fly.dev Visit your newly deployed app at https://hello_elixir.fly.dev/ ``` > [!NOTE] > > Make sure to note your Postgres credentials from the output. That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * `fly logs` - Tail your application logs * `fly status` - App deployment details * `fly status -a postgres-database-app-name` - Database deployment details * `fly deploy` - Deploy the application after making changes To recap, the `fly launch` command detected that we are using Phoenix, set up a Fly app, created a Fly Postgres app and configured it for us, updated our Dockerfile, and created special modules and scripts to run ecto migrations for us! ### Storing secrets on Fly.io You may also have some secrets you'd like set on your app. Use [`fly secrets`](/apps/secrets) to configure those. ```bash theme={null} fly secrets set MY_SECRET_KEY=my_secret_value ``` ### Deploying again When you want to deploy changes to your application, use `fly deploy`. ```bash theme={null} fly deploy ``` The `fly deploy` command uses a Fly.io remote builder app to build the image. You can always check on the status of a deploy: ```bash theme={null} fly status ``` Check your app logs: ```bash theme={null} fly logs ``` If everything looks good, open your app on Fly.io! ```bash theme={null} fly apps open ``` ### Important IPv6 settings The `flyctl` command attempts to modify your project's Dockerfile and append the following lines: ```Dockerfile theme={null} # Appended by flyctl ENV ECTO_IPV6 true ENV ERL_AFLAGS "-proto_dist inet6_tcp" ``` If you customized your Dockerfile or launched without the Dockerfile, this setting may not have been set for you. These values are important and enable your Elixir app to work smoothly in Fly's private IPv6 network. Check for this if you encounter network-related errors like the following: ``` Could not contact remote node my-app@fdaa:0:31d4:a5b:9d36:7c1e:f284:2, reason: :nodedown. Aborting... ``` ## What's Next? Next up, [IEx into Your Running App](/elixir/the-basics/iex-into-running-app)! # Legacy pre v1.6.3 Phoenix App Source: https://docs.fly.io/elixir/getting-started/legacy Deploy a Phoenix app generated before v1.6.3 to Fly.io: configure releases and IPv6, add a Dockerfile, set secrets, and attach a Fly Postgres database. Getting an application running on Fly.io is essentially working out how to package it as a deployable image and attach it to a database. Once packaged, it can be deployed to the Fly.io platform. In this guide we’ll learn how to deploy an [Elixir Phoenix](https://www.phoenixframework.org/) application on Fly.io and connect it to a PostgreSQL database. **This guide is for applications generated on Phoenix versions older than v1.6.3.** If you're on 1.6.3 or higher, deployment setup is baked into the framework! Check out our [guide for launching newer Elixir apps](/elixir/getting-started). ## *Running the Application Locally* Let's walk through working with Phoenix locally and preparing it for deployment. We can start with a brand new Phoenix application. ```bash theme={null} mix phx.new hello_elixir ``` **Tip:** Make sure you have a local [PostgreSQL server installed](https://wiki.postgresql.org/wiki/Detailed_installation_guides) and running. Now, we setup the database and start the application. ```bash cmd theme={null} mix ecto.setup ``` ```output output theme={null} The database for HelloElixir.Repo has been created ``` ```bash cmd theme={null} mix phx.server ``` ```output output theme={null} [info] Running HelloElixirWeb.Endpoint with cowboy 2.8.0 at 0.0.0.0:4000 (http) [info] Access HelloElixirWeb.Endpoint at http://localhost:4000 ``` Connect to localhost:4000 to confirm that you have a working Elixir application. ### *Generate Release Config Files* We use the `mix release.init` command to create some sample files in the `./rel` directory. ```bash cmd theme={null} mix release.init ``` ```output output theme={null} * creating rel/vm.args.eex * creating rel/remote.vm.args.eex * creating rel/env.sh.eex * creating rel/env.bat.eex ``` ## *Special Note on Fly Networking* Internally Fly uses IPv6 networking. This enables some cool features, but legacy Elixir applications need to be configured to work smoothly with it. These config changes tell Elixir, Phoenix, and the BEAM that we are using IPv6 addresses. The options look like `inet6` and `inet6_tcp`. The next steps are how we configure that in our application. We only need to configure `rel/env.sh.eex`. This file gets turned into a shell script that the release uses to set ENV values used when we run any release commands. Here's the important parts. ``` #!/bin/sh ip=$(grep fly-local-6pn /etc/hosts | cut -f 1) export RELEASE_DISTRIBUTION=name export ELIXIR_ERL_OPTIONS="-proto_dist inet6_tcp" ``` We configure the node to use a full node name when it runs. We get the Fly assigned IPv6 address and use that to name our node. Finally, we configure `inet6_tcp` for the BEAM as well. Even if you don't care to cluster your nodes together, you still want to do this because it enables running an IEx shell in a running node. ## *Docker Setup* ### *Dockerfile* You can find a good Dockerfile ready to build your application in the [hello\_elixir GitHub repo](https://github.com/fly-apps/hello_elixir/blob/main/Dockerfile). It is important to note that it uses a Debian-slim base image to build. This avoids DNS issues in Alpine images. The [base elixir image is maintained by the hexpm](https://hub.docker.com/r/hexpm/elixir/tags?page=1\&ordering=last_updated) org and is kept up to date. [Hex.pm](https://hex.pm/) is the official package repository for Elixir. There are other options instead of Debian there as well if you prefer. The Dockerfile uses a two-stage approach. There are two `FROM` commands. The first stage pulls in the source and builds the release. The second stage takes the prepared release and sets it up in a minimal Docker image. The final deployed image contains only our release. ### *Docker Ignore File* We add the file `.dockerignore` to the project with the following contents. Depending on how you `COPY` things into the Dockerfile, you may or may not need to configure this. ``` assets/node_modules/ deps/ ``` This ensures we keep any natively compiled Elixir or Node packages from our development environments from causing a problem in the Linux container. ## *Launch the App on Fly* To launch an app on fly, run `fly launch` in the directory with your source code. This creates and configures a fly app for you by inspecting your source code, then prompts you to deploy. ```bash cmd theme={null} fly launch ``` ```output output theme={null} $ fly launch Scanning source code Detected Dockerfile app ? Select region: sea (Seattle, Washington (US)) Created app fly-elixir in organization personal Wrote config file fly.toml ? Would you like to deploy now? No ``` Don't deploy it just yet. We're going to adjust the generated `fly.toml` file first. The `fly launch` command scans your source code to determine how to build a deployment image as well as identify any other configuration your app needs, such as secrets and exposed ports. After your source code is scanned and the results are printed, you'll be prompted for an organization. Organizations are a way of sharing applications and resources between Fly users. Every Fly account has a personal organization, called `personal`, which is only visible to your account. Let's select that for this guide. Next, you'll be prompted to select a region to deploy in. The closest region to you is selected by default. You can use this or change to another region. You can find the [list of supported regions here](/reference/regions). At this point, `flyctl` created an app for you and wrote your configuration to a `fly.toml` file. You'll then be prompted to build and deploy your app. Once complete, your app will be running on fly. ## *Inside `fly.toml`* The `fly.toml` file now contains a default configuration for deploying your app. In the process of creating that file, `flyctl` has also generated a Fly-side application slot with a new name. In this case, it is `fly-elixir`. If we look at the `fly.toml` file we can see the name in there: ```toml theme={null} app = "fly-elixir" kill_signal = "SIGINT" kill_timeout = 5 processes = [] [env] [experimental] allowed_public_ports = [] auto_rollback = true [[services]] http_checks = [] internal_port = 8080 processes = ["app"] protocol = "tcp" script_checks = [] [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s" ``` The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name/value at the start. That name is used to identify the application to the Fly platform. The rest of the file contains settings to be applied to the application when it deploys. We'll have more details about these properties as we progress, but for now, it's enough to say that they mostly configure which ports the application will be visible on. ## *Customizing `fly.toml`* Elixir applications need a little customization to the generated `fly.toml` file. ```toml theme={null} app = "fly-elixir" kill_signal = "SIGTERM" kill_timeout = 5 processes = [] [deploy] release_command = "/app/entry eval HelloElixir.Release.migrate" [env] [experimental] allowed_public_ports = [] auto_rollback = true [[services]] http_checks = [] internal_port = 4000 processes = ["app"] protocol = "tcp" script_checks = [] [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "30s" # allow some time for startup interval = "15s" restart_limit = 0 timeout = "2s" ``` There are two important changes here: * We added the `[deploy]` setting. This tells Fly that on a new deploy, **run our database migrations**. The Dockerfile we're using creates a symlink to your app name called `entry`. This uses that symlink run the migrate command. * The `kill_signal` is set to `SIGTERM`. An Elixir node does a clean shutdown when it receives a `SIGTERM` from the OS. * The `internal_port` is set to 4000 for our Elixir application. The port value should match your application. Some other values were tweaked as well. ## *Preparing to Deploy* We're almost there! Before we can deploy our new app, we need to setup a few things in our Fly account first. Namely, we want to provide the needed secrets and we need a database! ### *Setting our Secrets on Fly* Elixir has a mix task that can generate a new Phoenix key base secret. Let's use that. ```bash theme={null} mix phx.gen.secret ``` It generates a long string of random text. Let's store that as a secret for our app. When we run this command in our project folder, `flyctl` uses the `fly.toml` file to know which app we are setting the value on. ``` fly secrets set SECRET_KEY_BASE= ``` ### *Creating our Fly Postgres Database* ```bash cmd theme={null} fly postgres create ``` ```output output theme={null} ? App name: hello-elixir-db Automatically selected personal organization: Mark Ericksen ? Select region: sea (Seattle, Washington (US)) ? Select VM size: shared-cpu-1x - 256 ? Volume size (GB): 10 Creating postgres cluster hello-elixir-db in organization personal Postgres cluster hello-elixir-db created Username: Password: Hostname: hello-elixir-db.internal Proxy Port: 5432 PG Port: 5433 Save your credentials in a secure place, you won't be able to see them again! Monitoring Deployment 2 desired, 2 placed, 2 healthy, 0 unhealthy [health checks: 6 total, 6 passing] --> v0 deployed successfully Connect to postgres Any app within the personal organization can connect to postgres using the above credentials and the hostname "hello-elixir-db.internal." For example: postgres://:@hello-elixir-db.internal:5432 See the postgres docs for more information on next steps, managing postgres, connecting from outside fly: https://fly.io/docs/reference/postgres/ ``` We can take the defaults which select the lowest values for CPU, size, etc. This is perfect for getting started. ### *Attach our App to the Database* We use `flyctl` to attach our app to the database which also sets our needed `DATABASE_URL` ENV value. ```bash cmd theme={null} fly postgres attach hello-elixir-db ``` ```output output theme={null} Postgres cluster hello-elixir-db is now attached to fly-elixir The following secret was added to fly-elixir: DATABASE_URL=postgres://:@hello-elixir-db.internal:5432/fly_elixir?sslmode=disable ``` We can see the secrets that Fly is using for our app like this. ```bash cmd theme={null} fly secrets list ``` ```output output theme={null} NAME DIGEST DATE DATABASE_URL 830d8769ff33cba6c8b29d1cd6a6fbac 1m10s ago SECRET_KEY_BASE 84c992ac7ef334c21f2aaecd41c43666 9m20s ago ``` Looks like we're ready to deploy! ## *Deploying to Fly* To deploy your app, just run just run: ```bash theme={null} fly deploy ``` First, `flyctl` builds our Dockerfile and pushes it to a Fly container registry. This will lookup our `fly.toml` file, and get the app name `fly-elixir` from there. Then `flyctl` will start the process of deploying our application to the Fly platform. Flyctl returns you to the command line when it's done. ## *Viewing the Deployed App* Now that the application has been deployed, let's find out more about its deployment. The command `fly status` will give you all the essential details. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = fly-elixir Owner = personal Version = 3 Status = running Hostname = fly-elixir.fly.dev Deployment Status ID = 9762642f-baa4-e4df-c683-13f2ce26a6bc Version = v3 Status = successful Description = Deployment completed successfully Instances = 1 desired, 1 placed, 1 healthy, 0 unhealthy Instances ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED f617e72a 3 sea run running 1 total, 1 passing 0 1m34s ago ``` ## *Connecting to the App* The quickest way to browse your newly deployed application is with the `fly apps open` command. ```bash cmd theme={null} fly apps open ``` ```output output theme={null} Opening https://fly-elixir.fly.dev/ ``` Your browser will be sent to the displayed URL. Fly will auto-upgrade this URL to a HTTPS secured URL. ### *Special Note on Clustering* To make clustering your Elixir applications easier on Fly, in the `env.sh.eex` file, the `RELEASE_NODE` is named using the `$FLY_APP_NAME` and the IPv6 address. It will look something like this in practice. ``` fly-elixir@fdaa:0:1da8:a7b:ac2:216b:da3f:2 ``` ### *Runtime Config* When we created the app, the file `config/runtime.exs` was created for us. Now we need to update it. ```elixir theme={null} import Config if config_env() == :prod do database_url = System.get_env("DATABASE_URL") || raise """ environment variable DATABASE_URL is missing. For example: ecto://USER:PASS@HOST/DATABASE """ config :hello_elixir, HelloElixir.Repo, # IMPORTANT: Or it won't find the DB server socket_options: [:inet6], url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") secret_key_base = System.get_env("SECRET_KEY_BASE") || raise """ environment variable SECRET_KEY_BASE is missing. You can generate one by calling: mix phx.gen.secret """ # IMPORTANT: Get the app_name we're using app_name = System.get_env("FLY_APP_NAME") || raise "FLY_APP_NAME not available" config :hello_elixir, HelloElixirWeb.Endpoint, # IMPORTANT: tell our app about the host name to use when generating URLs url: [host: "#{app_name}.fly.dev", port: 80], http: [ ip: {0, 0, 0, 0, 0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "4000") ], secret_key_base: secret_key_base # IMPORTANT: Enable the endpoint for releases config :hello_elixir, HelloElixirWeb.Endpoint, server: true end ``` This code expects to receive some ENV values at runtime. We're expecting `SECRET_KEY_BASE` with our Phoenix secret, our `FLY_APP_NAME` from Fly, and the `DATABASE_URL` for connecting to a Fly hosted Postgres database. Also, you don't need to turn on TLS for connecting to the Postgres instance. Fly private networks operate over an encrypted WireGuard mesh, so traffic between application servers and PostgreSQL is already encrypted and there's no need to TLS. ## *Bonus Sections* With your application up and running, there are some additional things you can do to go further. Using some `flyctl` commands, we can easily do some powerful things with our application. These bonus tips cover: * Getting an IEx shell into your running node. This helps you manage and work with your running system. * Clustering multiple Elixir nodes together. Say "Hello!" to the power of Distributed Computing! * Scaling your application out to more machines and even distant regions (with or without clustering). ### *What is the IP Address?* If you want to know what IP addresses the app is using, try `fly ips list`: ```bash cmd theme={null} fly ips list ``` ```output output theme={null} TYPE ADDRESS CREATED AT v4 213.188.199.124 24m5s ago v6 2a09:8280:1:ce56:c80f:5071:6e94:6688 24m5s ago ``` # Migrate an Elixir App from Heroku Source: https://docs.fly.io/elixir/getting-started/migrate-from-heroku Move a Phoenix app with Postgres, Oban and a custom domain from Heroku to Fly.io: fly launch, import secrets, copy the database, and a command cheat sheet. This guide runs you through how to migrate a basic Elixir application off of Heroku and onto Fly.io. It assumes you're running the following services on Heroku: * Postgres database * Custom domain * Background worker, like Oban If your application is running with more services, additional work may be needed to migrate your application off Heroku. ## Migrating your app The steps below run you through the process of migrating your Phoenix app from Heroku to Fly. ### Provision and deploy Phoenix app to Fly From the root of the Elixir app you're running on Heroku, run `fly launch` and select the options to provision a new Postgres database. When you run `fly launch` from the newly-created project directory, the launcher provides some defaults for your new app, and gives you the option to tweak the settings. Run: ```sh theme={null} cd my_app_name fly launch ``` You'll get a summary of the defaults chosen for your app: ```output theme={null} Organization: MyOrgName (fly launch defaults to the personal org) Name: my_app_name (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes Opening https://fly.io/cli/launch/bea626e2d179a083a3ba622a367e24ec ... ``` Type `y` at the prompt to open the Fly Launch page, and make the following changes to your app config: * Change the default app name and region, if needed. * For Databases, select Fly Postgres, give the Postgres database app a name (for example, your app name with `-db` appended) and choose a configuration. Once you confirm your settings, you can return to the terminal, where the launcher will: * Run the deployment setup task * Build the image * Set secrets required by (`SECRET_KEY_BASE`, for example) * Deploy the application in your selected region ```output theme={null} Waiting for launch data... Done Created app 'my_app_name' in organization 'personal' Admin URL: https://fly.io/apps/my_app_name Hostname: my_app_name.fly.dev Set secrets on my_app_name: SECRET_KEY_BASE Creating postgres cluster in organization personal Creating app... Setting secrets on app my_app_name-db... Provisioning 1 of 1 machines with image flyio/postgres-flex:15.3@sha256:44b698752cf113110f2fa72443d7fe452b48228aafbb0d93045ef1e3282360a6 Waiting for machine to start... Machine 2865550c7e96d8 is created ==> Monitoring health checks Waiting for 2865550c7e96d8 to become healthy (started, 3/3) Postgres cluster my_app_name-db created Username: postgres Password: EChe3BrhCjsPQEI Hostname: my_app_name-db.internal Flycast: fdaa:2:45b:0:1::1d Proxy port: 5432 Postgres port: 5433 Connection string: postgres://postgres:EChe3BrhCjsPQEI@my_app_name-db.flycast:5432 Save your credentials in a secure place -- you won't be able to see them again! Connect to postgres Any app within the MyOrgName organization can connect to this Postgres using the above connection string Now that you've set up Postgres, here's what you need to understand: https://fly.io/docs/postgres/getting-started/what-you-should-know/ Checking for existing attachments Registering attachment Creating database Creating user Postgres cluster my_app_name-db is now attached to my_app_name The following secret was added to my_app_name: DATABASE_URL=postgres://aa_hello_elixir2:Er6pLzUBuhKcbBl@my_app_name-db.flycast:5432/aa_hello_elixir2?sslmode=disable Postgres cluster my_app_name-db is now attached to my_app_name Generating rel/env.sh.eex for distributed Elixir support Preparing system for Elixir builds Installing application dependencies Running Docker release generator Wrote config file fly.toml Validating /Users/anderson/test-elixir-gs/hello_elixir2/fly.toml ✓ Configuration is valid ==> Building image Remote builder fly-builder-black-pine-7645 ready Remote builder fly-builder-black-pine-7645 ready ==> Building image with Docker --> docker host: 20.10.12 linux x86_64 ... --> Pushing image done image: registry.fly.io/my_app_name:deployment-01HPMGHTG8XSYH3ZCV82SF5CEZ image size: 126 MB Watch your deployment at https://fly.io/apps/my_app_name/monitoring Provisioning ips for my_app_name Dedicated ipv6: 2a09:8280:1::2a:bc0b:0 Shared ipv4: 66.241.124.79 Add a dedicated ipv4 with: fly ips allocate-v4 Running my_app_name release_command: /app/bin/migrate ------- ✔ release_command 784eee4c294298 completed successfully ------- This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Checking DNS configuration for my_app_name.fly.dev Visit your newly deployed app at https://my_app_name.fly.dev/ ``` > [!NOTE] > > Make sure to note your Postgres credentials from the output. That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * `fly logs` - Tail your application logs * `fly status` - App deployment details * `fly status -a postgres-database-app-name` - Database deployment details * `fly deploy` - Deploy the application after making changes There's still work to be done to move more Heroku stuff over, so don't worry if the app doesn't boot right away. Also check out: * [Connect via IEx](/elixir/the-basics/iex-into-running-app) ### Transfer Heroku secrets To see all of your Heroku env vars and secrets, run: ```bash theme={null} heroku config -s | grep -v -e "DATABASE_URL" | fly secrets import ``` This command exports the Heroku secrets, excludes `DATABASE_URL` and imports them into Fly. Verify your Heroku secrets are in Fly. ```bash theme={null} fly secrets list NAME DIGEST CREATED AT DATABASE_URL 24e455edbfcf1247a642cdae30e14872 14m29s ago LANG 95a7bb7a8d0ee402edde95bb78ef95c7 1m24s ago MIX_ENV fd89784e59c72499525556f80289b2c7 1m26s ago SECRET_KEY_BASE 5afb43c2ddbba6c02ffa7e2834689692 1m22s ago ``` ### Transfer the Database > [!NOTE] > > Any new data created by your Heroku app during this database migration won't be moved over to Fly.io. Consider taking your Heroku application offline or place in read-only mode if you want to be confident that this migration will move over 100% of your Heroku data to Fly.io. Set the `HEROKU_DATABASE_URL` variable in your Fly.io environment. ```bash theme={null} fly secrets set HEROKU_DATABASE_URL=$(heroku config:get DATABASE_URL) ``` Alright, lets start the transfer remotely on the Fly.io instance. ```bash theme={null} fly ssh console ``` Then from the remote Fly SSH console transfer the database. ```bash theme={null} pg_dump -Fc --no-acl --no-owner -d $HEROKU_DATABASE_URL | pg_restore --verbose --clean --no-acl --no-owner -d $DATABASE_URL ``` You may need to upgrade your Heroku database to match the version of the source Fly.io database. Refer to Heroku's [Upgrading the Version of a Heroku Postgres Database](https://devcenter.heroku.com/articles/upgrading-heroku-postgres-databases) for instructions on how to upgrade, then try the command above again. After the database transfers unset the `HEROKU_DATABASE_URL` variable. ```bash theme={null} fly secrets unset HEROKU_DATABASE_URL ``` Then launch your Heroku app to see if its running. ``` fly apps open ``` If you have a Redis server, there's a good chance you need to set that up. ### Custom Domain & SSL Certificates After you finish deploying your application to Fly.io and have tested it extensively, [read through the Custom Domain docs](/networking/custom-domain) and point your domain at it. In addition to supporting [`CNAME` DNS records](/networking/custom-domain#cname-records), Fly.io also supports [`A` and `AAAA` records](/networking/custom-domain#a-and-aaaa-records-recommended) for those who want to point `example.com` (without the `www.example.com`) directly at their App on Fly.io. ## Cheat Sheet Old habits die hard, especially good habits like deploying frequently to production. Below is a quick overview of the differences you'll notice initially between Fly.io and Heroku. ### Commands flyctl commands are a bit different than Heroku, but you'll get use to them after a few days. | Task | Heroku | Fly.io | | - | - | - | | Deployments | `git push heroku` | `fly deploy` | | IEx console | `heroku console` | `fly ssh console --pty -C "/app/bin/app remote"` | | Database migration | `heroku rake db:migrate` | `fly ssh console -C "/app/bin/migrate"` | | Postgres console | `heroku psql` | `fly postgres connect -a ` | | Tail log files | `heroku logs` | `fly logs` | | View configuration | `heroku config` | `fly ssh console -C "printenv"` | | View releases | `heroku releases` | `fly releases` | | Help | `heroku help` | `fly help` | Check out the [flyctl docs](/flyctl) for a more extensive inventory of flyctl commands. ### Deployments By default Heroku deployments are kicked off via the `git push heroku` command. Fly.io works a bit differently by kicking of deployments via `fly deploy`—git isn't needed to deploy to Fly.io. The advantage to this approach is your git history will be clean and not full of commits like `git push heroku -am "make app work"` or `git push heroku -m "ok it will really work this time"`. To achieve the desired `git push` behavior, we recommend setting up `fly deploy` as the final command in your continuous integration pipeline, as outlined for GitHub in the [Continuous Deployment with Fly.io and GitHub Actions](/launch/continuous-deployment-with-github-actions) docs. #### Deploy via git Heroku's default deployment technique is via `git push heroku`. Fly.io doesn't require a git commit, just run `fly deploy` and the files on your local workstation will be deployed. Fly.io can be configured to deploy on git commits with the following techniques with a [GitHub Action](/launch/continuous-deployment-with-github-actions). ### Databases Fly.io and Heroku have different Postgres database offerings. The most important distinction to understand about using Fly.io is that it automates provisioning, maintenance, and snapshot tasks for your Postgres database, but it does not manage it. If you run out of disk space, RAM, or other resources on your Fly Postgres instances, you'll have to scale those virtual machines from the [Fly CLI](/unmanaged-postgres). Contrast that with Heroku, which fully manages your database and includes an extensive suite of tools to provision, backup, snapshot, fork, patch, upgrade, and scale up/down your database resources. The good news for people who want a highly managed Postgres database is they can continue hosting it at Heroku and point their Fly.io instances to it! #### Heroku's managed database One command is all it takes to point Fly Apps at your Heroku managed database. ```bash theme={null} fly secrets set DATABASE_URL=$(heroku config:get DATABASE_URL) ``` This is a great way to get comfortable with Fly.io if you prefer a managed database provider. In the future if you decide you want to migrate your data to Fly, you can do so [pretty easily with a few commands](#transfer-the-database). #### Fly Postgres The most important thing you'll want to be comfortable with using Fly.io's database offering is [backing up and restoring your database](/unmanaged-postgres/managing/backup-and-restore). As your application grows, you'll probably first [scale disk and RAM resources](/unmanaged-postgres/managing/scaling), then [scale out with multiple replicas](/unmanaged-postgres/managing/horizontal-scaling). Common maintenance tasks will include [upgrading Postgres](/unmanaged-postgres/managing/upgrades) as new versions are released with new features and security updates. [See here](/unmanaged-postgres) for a more comprehensive guide for what's required when running your Postgres databases on Fly.io. ### Pricing Heroku and Fly.io have very different pricing structures. You'll want to read through the details on [Fly.io's pricing page](/about/pricing) before launching to production. The sections below serve as a rough comparison between Heroku's and Fly.io's plans as of August 2022. > [!NOTE] > > Please do your own comparison of plans before switching from Heroku to Fly.io. The examples below are illustrative estimates between two very different offerings, which focuses on the costs of app & database servers. It does not represent the final costs of each plan. Also, the prices below may not be immediately updated if Fly.io or Heroku change prices. #### Free Plans Heroku will not offer free plans as of November 28, 2022. Fly.io offers free usage for up to 3 full time VMs with 256MB of RAM, which is enough to run a Elixir app and Postgres database to get a feel for how Fly.io works. #### Plans for Small Elixir Apps Heroku's Hobby tier is limited to 10,000 rows of data, which gets exceeded pretty quickly requiring the purchase of additional rows of data. | Heroku Resource | Specs | Price | | - | - | - | | App Dyno | 512MB RAM | \$7/mo | | Database | 10,000,000 rows | \$9/mo | | **Estimated cost** | | \$16/mo | Fly.io pricing is [metered for the resources](/about/pricing) you use. Database is billed by the amount of RAM and disk space used, not by rows. The closest equivalent to the Heroku Hobby tier on Fly.io looks like this: | Fly.io Resource | Specs | Price | | - | - | - | | App Server | 1GB RAM | \~\$6.70/mo | | Database Server | 256MB RAM / 10Gb disk | \~\$3.44/mo | | **Estimated cost** | | \~\$9.14/mo | #### Plans for Medium to Elixir Apps There's too many variables to compare Fly.io's and Heroku's pricing for larger Elixir applications depending on your needs, so you'll definitely want to do your homework before migrating everything to Fly.io. This comparison focuses narrowly on the costs of app & database resources, and excludes other factors such as bandwidth costs, bundled support, etc. | Heroku Resource | Specs | Price | Quantity | Total | | - | - | - | - | - | | App Dyno | 2.5GB RAM | \$250/mo | 8 | \$2,000/mo | | Database | 61GB RAM / 1TB disk | \$2,500/mo | 1 | \$2,500/mo | | **Estimated cost** | | | | \$4,500/mo | Here's roughly the equivalent resources on Fly: | Fly.io Resource | Specs | Price | Quantity | Total | | - | - | - | - | - | | App Server | 4GB RAM / 2X CPU | \~\$66.00/mo | 8 | \~\$528/mo | | Database Server | 64GB RAM / 500GB disk | \~\$633/mo | 2 | \~\$1,266/mo | | **Estimated cost** | | | | \~\$1,762/mo | Again, the comparison isn't realistic because it focuses only on application and database servers, but it does give you an idea of how the different cost structures scale on each platform. For example, Heroku's database offering at this level is redundant, whereas Fly.io offers 2 database instances to achieve similar levels of redundancy. # Elixir on Fly.io Source: https://docs.fly.io/elixir/index Deploy Phoenix and LiveView apps to Fly.io and cluster Elixir nodes across regions. Jump to getting started, the basics, or advanced BEAM guides. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/elixir-intro.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=86b52a16965f90e709e0dcffdd671dee) Fly.io makes it easier to run an Elixir cluster on multiple servers around the world and deploy to those regions with a single command. This comprehensive guide walks you through how to launch your first Phoenix app on Fly.io and includes a few additional guides to walk you through more advanced topics. When you deploy your clustered application around the world, you give your users a fast, snappy, low-latency experience. This becomes particularly important if you're deploying a Phoenix LiveView app. ## [Getting Started](./getting-started) Run through the [Elixir Getting Started](./getting-started/) guide to get a feel for what it's like to deploy to Fly.io. This is the best jumping-off point for getting your app up and running. ## [The Basics](./the-basics) Go from running an app to clustering your app globally and administering with Fly.io. ## [Advanced Guides](./advanced-guides) A collection of guides for doing some extra nifty things with your app on Fly.io. # Elixir: The Basics Source: https://docs.fly.io/elixir/the-basics Elixir basics on Fly.io: open an IEx shell into a running production app, cluster Erlang nodes into one distributed network, and troubleshoot deploys. Most Elixir applications require additional setup beyond provisioning a Postgres database. These guides will help you get through the basics of setting up your Elixir application with pieces of infrastructure commonly found in medium-to-high complexity Elixir applications. ## IEx into Your Running App Get an IEx shell into your running production app on Fly! ## Clustering Your Application Cluster your erlang nodes into a single distributed network! ## Troubleshooting Common troubleshooting issues and resources for getting unstuck. # Clustering Your Application Source: https://docs.fly.io/elixir/the-basics/clustering Cluster Elixir nodes on Fly.io with dns_cluster and node names built from the app name and private IPv6 address, then scale across regions with a static cookie. Elixir and the BEAM have the incredible ability to be clustered together and processes can pass messages seamlessly to each other between nodes. Fly.io makes clustering easy! This extra (and totally optional) portion of the guide walks you through clustering your Elixir application. There are 3 parts to getting clustering quickly setup on Fly.io. * Naming the Node(s) * Installing and using `dns_cluster` * Scaling our application to multiple VMs ## Naming the Node(s) To make clustering easier, we want our Elixir nodes to be named using our Fly application name and the IPv6 address assigned to the node. Then later, our cluster can form up using DNS settings based on this naming scheme. If you've run `fly launch` already then you should have a `rel` folder with a `rel/env.sh.eex` file already. If not, run this command from your Elixir application: ```bash theme={null} mix release.init ``` Then check the generated `rel/env.sh.eex` file and add ensure the lines looks similar: ```shell theme={null} # configure node for distributed erlang with IPV6 support export ERL_AFLAGS="-proto_dist inet6_tcp" export ECTO_IPV6="true" export DNS_CLUSTER_QUERY="${FLY_APP_NAME}.internal" export RELEASE_DISTRIBUTION="name" export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}" ``` This names our Elixir node's name (also known as RELEASE\_NODE) using the Fly application name, the Docker image reference value, and the internal IPv6 address. Make sure to deploy after making this change! ```bash theme={null} fly deploy ``` ## Adding `dns_cluster` The Phoenix library [dns\_cluster](https://github.com/phoenixframework/dns_cluster) helps here. The `dns_cluster` library lets you easily setup Erlang Clustering using DNS, and Fly.io has built in DNS support! After installing `dns_cluster`, add it to the application like this: ```elixir theme={null} defmodule HelloElixir.Application do use Application def start(_type, _args) do children = [ # ... # setup for clustering {Phoenix.PubSub, ...}, {DNSCluster, query: Application.get_env(:hello_elixir, :dns_cluster_query) || :ignore}, HelloElixirWeb.Endpoint ] # ... end # ... end ``` Our next step is to add the `dns_cluster_query` configuration to the file `config/runtime.exs`. ```elixir theme={null} config :hello_elixir, dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY") ``` And finally add a key to our `fly.toml` file: ```toml theme={null} [env] DNS_CLUSTER_QUERY = "hello-elixir.internal" ``` **REMEMBER:** Deploy your updated app so the clustering code is available, with `fly deploy`. This configures `dns_cluster` to look for other deployed apps using the same `$FLY_APP_NAME` on the `.internal` private network. This assumes that your `rel/env.sh.eex` file is configured to name your Elixir node using the `$FLY_APP_NAME`. We did this earlier in the "Naming Your Elixir Node" section. Before this app can be clustered, we need more than one VM. We'll do that next! ## Running multiple VMs There are two ways to run multiple VMs. 1. Scale our application to have multiple Fly Machines in one region. 2. Add a Machine to another region (multiple regions). Both approaches are valid and our Elixir application doesn't change at all for the approach you choose! Let's first start with a baseline of our single deployment. ```bash cmd theme={null} fly status ``` ```output output theme={null} ... Machines PROCESS ID VERSION REGION STATE CHECKS LAST UPDATED app 6e82dd00f75687 20 sea started 1 total, 1 passing 2023-03-16T22:01:45Z ``` ### Scaling in a single region Let's scale up to 2 VMs in our current region. ```bash theme={null} fly scale count 2 ``` Checking on the status we can see what happened. ```bash cmd theme={null} fly status ``` ```output output theme={null} ... Machines PROCESS ID VERSION REGION STATE CHECKS LAST UPDATED app 5683d474b4658e 20 sea started 1 total, 1 passing 2023-06-16T01:49:36Z app 6e82dd00f75687 20 sea started 1 total, 1 passing 2023-03-16T22:01:45Z ``` We now have two VMs in the same region! That was easy. Let's make sure they are clustered together. We can check the logs: ```bash cmd theme={null} fly logs ``` ```output output theme={null} ... app[5683d474b4658e] sea [info] 21:50:21.924 [info] [libcluster:fly6pn] connected to :"fly-elixir@fdaa:0:1da8:a7b:ac2:f901:4bf7:2" ... ``` But that's not as rewarding as seeing it from inside a node. From an IEx shell, we can ask the node we're connected to, what other nodes it can see. ```bash theme={null} fly ssh console --pty -C "/app/bin/hello_elixir remote" ``` ```elixir theme={null} iex(fly-elixir@fdaa:0:1da8:a7b:ac2:f901:4bf7:2)1> Node.list [:"fly-elixir@fdaa:0:1da8:a7b:ac4:eb41:19d3:2"] ``` I included the IEx prompt because it shows the IP address of the node I'm connected to. Then getting the `Node.list` returns the other node. Our two VMs are connected and clustered! ### Scaling to multiple regions Fly.io makes it super easy to run VMs of your applications physically closer to your users. Through the magic of DNS, users are directed to the nearest [region](/reference/regions) where your application is located. Starting back from our baseline of a single VM running in `sea` which is Seattle, Washington (US), I'll add the region `ewr` which is NJ (US). I can do this by cloning the existing Fly Machine into my desired region: ```bash theme={null} fly machine clone 6e82dd00f75687 --region ewr ``` Now our status shows we have two Machines spread across 2 regions! This puts a VM on both coasts of the US. ```bash cmd theme={null} fly status ``` ```output output theme={null} ... Machines PROCESS ID VERSION REGION STATE CHECKS LAST UPDATED app 0e2869ea63d486 20 ewr started 1 total, 1 passing 2023-06-16T01:56:19Z app 6e82dd00f75687 20 sea started 1 total, 1 passing 2023-03-16T22:01:45Z ``` Let's ensure they are clustered together. ```bash theme={null} fly ssh console --pty -C "/app/bin/hello_elixir remote" ``` ```elixir theme={null} iex(fly-elixir@fdaa:0:1da8:a7b:ac2:cdf6:c422:2)1> Node.list [:"fly-elixir@fdaa:0:1da8:a7b:ab2:a8e:6666:2"] ``` We have two VMs of our application deployed to the West and East coasts of the North American continent and they are clustered together! Our users will automatically be directed to the server nearest them. That is so cool! ## The cookie situation Before two Elixir nodes **can** cluster together, they must share a secret cookie. The cookie itself isn't meant to be a super secret encryption key or anything like that, it's designed to let us create multiple sets of small clusters on the same network that don't all just connect together. Different cookies means different clusters. For instance, only the nodes that all use the cookie `abc` will connect together. For us, this means that in order for `my_remote` node to connect to the cluster on Fly, we need to share the same cookie value used in production. ### The cookie problem When we build a `mix release`, it generates a long random string for the cookie value. When we **re-run** the `mix release` command, it keeps the same cookie value. That is, when we don't run it in Docker. The Dockerfile we're using is building a fresh release every time we run it. That's kind of the point of a Docker container. So **our cookie value is being randomly generated every time we deploy**. This means after every deploy, we would have to figure out what the new cookie value is so our local node can use it. ### The cookie solution The easiest solution here is to **specify** the value to use for our cookie. One that we will know outside of the build and that won't keep changing on us. ## Making the cookie changes If we read the [Mix.Tasks.Release docs](https://hexdocs.pm/mix/Mix.Tasks.Release.html#module-options), in the `:cookie` section we learn that if we provide an ENV named `RELEASE_COOKIE`, it will be used. If that ENV is not found, it falls back to the randomly generated one. To generate the cookie string we will use this Elixir command: ```elixir theme={null} Base.url_encode64(:crypto.strong_rand_bytes(40)) ``` To provide the ENV named `RELEASE_COOKIE` inside the running app, after generating the cookie, we can either: * [Put it as a secret](/apps/secrets#set-secrets) inside project settings under the `RELEASE_COOKIE` name, or * Store it in our `fly.toml` file like this: ```toml theme={null} [env] RELEASE_COOKIE = "my-app-cookie" ``` After setting up the ENV and deploying the application, we can verify that the cookie is being used by getting an [IEx shell into our running server](/elixir/the-basics/iex-into-running-app) and issuing the following command: ```elixir theme={null} Node.get_cookie() ``` This shows the cookie being used at runtime. We can also check list of connected nodes by running the following command: ```elixir theme={null} Node.list() ``` An empty list means the node has no connections. If you are sure that there is more then one node running, you could proceed to [Troubleshooting](/elixir/the-basics/troubleshooting) documentation. With a known and unchanging cookie deployed in our application, we are ready for the next step! ### Important IPv6 settings The `flyctl` command attempts to modify your project's Dockerfile and append the following lines: ```Dockerfile theme={null} # Appended by flyctl ENV ECTO_IPV6 true ENV ERL_AFLAGS "-proto_dist inet6_tcp" ``` If you customized your Dockerfile or launched without the Dockerfile, this setting may not have been set for you. These values are important and enable your Elixir app to work smoothly in Fly's private IPv6 network. Check for this If you encounter network related errors like this: ``` Could not contact remote node my-app@fdaa:0:31d4:a5b:9d36:7c1e:f284:2, reason: :nodedown. Aborting... ``` If you have non-empty list with all of your running nodes - congratulations, you have successfully set up the clustering! # IEx into Your Running App Source: https://docs.fly.io/elixir/the-basics/iex-into-running-app Open an IEx shell into a running Elixir app on Fly.io with fly ssh console and the --pty flag, and wrap the command in a short shell script for quick access. Elixir supports getting a IEx shell into a running production node. How cool is that? ## SSH Into our App To do this, we will [login with SSH](/flyctl/cmd/fly_ssh) to our application VM. There is a one-time setup task for using SSH. Follow the instructions. ``` fly ssh issue --agent ``` With SSH configured, let's open a console. ```bash cmd theme={null} fly ssh console --pty -C "/app/bin/hello_elixir remote" ``` ```output output theme={null} Connecting to hello_elixir.internal... complete Erlang/OTP 23 [erts-11.2.1] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help) iex(hello_elixir@fdaa:0:1da8:a7b:ac4:b204:7e29:2)1> ``` You have a live IEx shell into your application! > [!NOTE] > > **Tip:** One of several ways to [exit the IEx shell](https://hexdocs.pm/iex/IEx.html#module-exiting-the-shell) is to hit `Ctrl+C, Ctrl+C`; to log out of the VM console, use `Ctrl+D` or `exit`. > [!NOTE] > > The `--pty` flag tells the SSH server to run the command in a pseudo-terminal. You will generally need this only when running interactive commands, like IEx. ## Creating a Shell Script for Convenience In UNIX-based operating system like Linux and Mac OS, we can create a shell script to make connecting to the server with an IEx terminal easy. The same can be done with a Windows PowerShell script or batch file. However we name the file will be our "command" for opening the IEx shell into the server. Here we'll use `fiex`, short for "Fly IEx". ```shell theme={null} #!/bin/bash set -e fly ssh console --pty --select -C "/app/bin/hello_elixir remote" ``` The `--pty` flag supports our interactive terminal and is needed to support the `--remsh` option used when we call `remote`. The `--select` flag will prompt for which instance to connect to. This is handy when deployed to multiple regions and we want to be explicit about where we connect. Remember to make the script executable: `chmod +x fiex`. Using a terminal from the app directory, type `./fiex` to quickly jump into an IEx shell on the server. ## What's Next? With another quick update we can prepare our application for clustering by naming our nodes differently. Next up, [Clustering Your Application](/elixir/the-basics/clustering)! # Troubleshooting Source: https://docs.fly.io/elixir/the-basics/troubleshooting Troubleshoot Elixir deploys on Fly.io: test releases and Docker builds locally, check IPv6 config, tune connection limits, and debug clustering. Some problems are harder to diagnose because they deal with [Elixir releases](https://hexdocs.pm/mix/master/Mix.Tasks.Release.html) or Docker build problems. Typically, you don't run the application that way locally, so you only encounter those problems when it's time to deploy. Here are a few tips to help diagnose and identify problems. * Run `mix release` locally on your project. * Build the Dockerfile locally to verify it builds correctly. `docker build .` * Check the `:prod` config in `config/runtime.exs`, which is not used locally. Carefully review it. * Run `fly logs` to check server logs. For diagnosing database app issues, refer to the [Postgres Monitoring](/unmanaged-postgres/managing/monitoring) information. Here's a quick hit list of commands to help: * Run `fly logs -a ` to check database app's server logs. * Run `fly checks list -a ` to check the database app's health. * Run `fly status -a --all` to see if any VMs failed. * Run `fly vm status -a ` to debug a specific VM. ## *Diagnosis Tip* Most difficulties center around application config. Applications generated with an older version of Phoenix are configured differently than a newly generated app. If you have problems like connecting to your database, usually an IPv6 configuration update is needed. The internal networks at Fly.io use a IPv6 addresses. Elixir/OTP needs some config to work smoothly. One way to identify an issue is to generate a new Elixir application using a current version of Phoenix. Deploy that to Fly.io with a database. With that, you have a local working example to compare against. Don't worry, you can easily [`destroy`](/flyctl/cmd/fly_apps_destroy) the test app when you're ready to. Suggested files to pay attention to when looking for config differences. * `config/config.exs` * `config/prod.exs` * `config/runtime.exs` * `Dockerfile` * `mix.exs` ## *Not Enough Connections* A common failure mode is the application exhausting the number of free connections, your default `fly.toml` has the following settings: ```toml theme={null} [services.concurrency] hard_limit = 50 soft_limit = 25 type = "connections" ``` Setting the `hard_limit` and `soft_limit` closer to your needs will free up the number of live connections per node. A safe starting point could be 1000 for the hard\_limit and 975 for the soft\_limit. The "right" amount depends on how much data is actively stored in the LiveView processes. That value will vary for each application. ## *Clustering* Here are some troubleshooting tips when working with [Clustering](/elixir/the-basics/clustering). When using [IEx to remote into a running application](/elixir/the-basics/iex-into-running-app) to diagnose connection issues, note if you see this warning when connecting: ``` warning: the --remsh option will be ignored because IEx is running on limited shell ``` When that warning is present, the Elixir node we are connecting to is not the remote running node. A new node was launched when the remote shell request was ignored. Refer to the [docs here about the `--pty`](/elixir/the-basics/iex-into-running-app) option. Another indication of this situation is when typing `Node.self()`, the name of the node is returned with a prefix similar to "rem-ea46-". Example: ``` :"rem-ea46-hello_elixir@fdaa:0:1da8:a7b:115:5641:7e85:2" ``` A working remote shell will return a node name more like this: ``` :"hello_elixir@fdaa:0:1da8:a7b:115:5641:7e85:2" ``` ### Testing Node Connectivity Using `libcluster` is an easy way to [auto-cluster](#clustering) an Elixir application. However, going through the process manually can help diagnose issues. We can open two terminals locally on our machine. In terminal A, we get an [IEx terminal](/elixir/the-basics/iex-into-running-app) to one node. Then in terminal B, we get an IEx terminal to a different node. In each terminal, we can ask the node for it's name: ``` Node.self ``` Then, taking the response node in terminal A, we can explicitly try to connect through terminal B (on the other node). That command might look like this: ``` Node.connect(:"result-from-terminal-A@ipvs-address") ``` If the result is `true` then it either connected to the other node or we entered into the wrong terminal and it says it's connected to itself. If we received a `true` response, we can check the list of connected nodes using this command: ``` Node.list() ``` An empty list means is has no connections. During the process, make note of any logged messages that might help explain why the two nodes can't connect. # fly agent Source: https://docs.fly.io/flyctl/cmd/fly_agent The Fly agent is a background process that manages wireguard connections started by flyctl. Commands such as 'fly ssh' and 'fly proxy' use this agent. Generally, the agent starts and stops automatically. These commands are useful for debugging. ## Usage ``` fly agent [command] [flags] ``` ## Available Commands * [ping](/flyctl/cmd/fly_agent_ping) - Ping the Fly agent * [restart](/flyctl/cmd/fly_agent_restart) - Restart the Fly agent * [run](/flyctl/cmd/fly_agent_run) - Run the Fly agent in the foreground * [start](/flyctl/cmd/fly_agent_start) - Start the Fly agent * [stop](/flyctl/cmd/fly_agent_stop) - Stop the Fly agent ## Options ``` -h, --help help for agent ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly agent ping Source: https://docs.fly.io/flyctl/cmd/fly_agent_ping Ping the Fly agent ## Usage ``` fly agent ping [flags] ``` ## Options ``` -h, --help help for ping -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 agent](/flyctl/cmd/fly_agent) - Commands that manage the Fly agent, a background process that manages flyctl wireguard connections # fly agent restart Source: https://docs.fly.io/flyctl/cmd/fly_agent_restart Restart the Fly agent ## Usage ``` fly agent restart [flags] ``` ## Options ``` -h, --help help for restart ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly agent](/flyctl/cmd/fly_agent) - Commands that manage the Fly agent, a background process that manages flyctl wireguard connections # fly agent run Source: https://docs.fly.io/flyctl/cmd/fly_agent_run Run the Fly agent in the foreground ## Usage ``` fly agent run [flags] ``` ## Options ``` -h, --help help for run ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly agent](/flyctl/cmd/fly_agent) - Commands that manage the Fly agent, a background process that manages flyctl wireguard connections # fly agent start Source: https://docs.fly.io/flyctl/cmd/fly_agent_start Start the Fly agent ## Usage ``` fly agent start [flags] ``` ## Options ``` -h, --help help for start ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly agent](/flyctl/cmd/fly_agent) - Commands that manage the Fly agent, a background process that manages flyctl wireguard connections # fly agent stop Source: https://docs.fly.io/flyctl/cmd/fly_agent_stop Stop the Fly agent ## Usage ``` fly agent stop [flags] ``` ## Options ``` -h, --help help for stop ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly agent](/flyctl/cmd/fly_agent) - Commands that manage the Fly agent, a background process that manages flyctl wireguard connections # fly apps Source: https://docs.fly.io/flyctl/cmd/fly_apps Manage your Fly applications. ## Usage ``` fly apps [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_apps_create) - Create a new application. * [destroy](/flyctl/cmd/fly_apps_destroy) - Permanently destroy one or more apps. * [errors](/flyctl/cmd/fly_apps_errors) - View application errors on Sentry.io * [list](/flyctl/cmd/fly_apps_list) - List applications. * [move](/flyctl/cmd/fly_apps_move) - Move an app to another organization. * [open](/flyctl/cmd/fly_apps_open) - Open browser to current deployed application * [releases](/flyctl/cmd/fly_apps_releases) - List app releases * [restart](/flyctl/cmd/fly_apps_restart) - Restart an application. ## Options ``` -h, --help help for apps ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly apps create Source: https://docs.fly.io/flyctl/cmd/fly_apps_create Create a new application on the Fly platform. This command won't generate a fly.toml configuration file, but you can fetch one with 'fly config save -a \'. ## Usage ``` fly apps create [flags] ``` ## Options ``` --generate-name Generate an app name -h, --help help for create -j, --json JSON output --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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps destroy Source: https://docs.fly.io/flyctl/cmd/fly_apps_destroy Delete one or more applications from the Fly platform. ## Usage ``` fly apps destroy [flags] ``` ## Options ``` -h, --help help for destroy -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps errors Source: https://docs.fly.io/flyctl/cmd/fly_apps_errors View application errors on Sentry.io ## Usage ``` fly apps errors [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for errors ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps list Source: https://docs.fly.io/flyctl/cmd/fly_apps_list List the applications currently available to this user. The list includes applications from all the organizations the user is a member of. The list shows the name, owner (org), status, and date/time of latest deploy for each app. Apps on a non-default network also show the network name. ## Usage ``` fly apps list [flags] ``` ## Options ``` -h, --help help for list -j, --json JSON output -o, --org string The target Fly.io organization -q, --quiet Only list app names ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps move Source: https://docs.fly.io/flyctl/cmd/fly_apps_move Move an application to another organization the current user belongs to. For details, see [https://fly.io/docs/apps/move-app-org/](https://fly.io/docs/apps/move-app-org/). ## Usage ``` fly apps move [flags] ``` ## Options ``` -h, --help help for move -o, --org string The target Fly.io organization --skip-health-checks Update machines without waiting for health checks -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps open Source: https://docs.fly.io/flyctl/cmd/fly_apps_open Open browser to current deployed application. If an optional relative URI is specified, it is appended to the root URL of the deployed application. ## Usage ``` fly apps open [RELATIVE_URI] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for open ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps releases Source: https://docs.fly.io/flyctl/cmd/fly_apps_releases List all the releases of the application onto the Fly platform, including type, when, success/fail and which user triggered the release. ## Usage ``` fly apps releases [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for releases --image Display the Docker image reference of the release -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 apps](/flyctl/cmd/fly_apps) - Manage apps. # fly apps restart Source: https://docs.fly.io/flyctl/cmd/fly_apps_restart Restart an application. Perform a rolling restart against all running Machines. ## Usage ``` fly apps restart [flags] ``` ## Options ``` --force-stop Performs a force stop against the target Machine -h, --help help for restart --skip-health-checks Restarts app without waiting for health checks ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly apps](/flyctl/cmd/fly_apps) - Manage apps. # fly auth Source: https://docs.fly.io/flyctl/cmd/fly_auth Authenticate with Fly (and logout if you need to). If you do not have an account, start with the AUTH SIGNUP command. If you do have an account, begin with the AUTH LOGIN subcommand. ## Usage ``` fly auth [command] [flags] ``` ## Available Commands * [docker](/flyctl/cmd/fly_auth_docker) - Authenticate docker * [login](/flyctl/cmd/fly_auth_login) - Log in a user * [logout](/flyctl/cmd/fly_auth_logout) - Logs out the currently logged in user * [signup](/flyctl/cmd/fly_auth_signup) - Create a new fly account * [whoami](/flyctl/cmd/fly_auth_whoami) - Displays the users email address/service identity currently authenticated and in use. ## Options ``` -h, --help help for auth ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly auth docker Source: https://docs.fly.io/flyctl/cmd/fly_auth_docker 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 that tokens may expire. ## Usage ``` fly auth docker [flags] ``` ## Options ``` -h, --help help for docker ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly auth](/flyctl/cmd/fly_auth) - Manage authentication # fly auth login Source: https://docs.fly.io/flyctl/cmd/fly_auth_login Logs a user into the Fly platform. Supports browser-based, email/password and one-time-password authentication. Defaults to using browser-based authentication. Use --code to finish a browser login that is waiting for approval, with the one-time code the approval page shows: for example when the browser is on another machine, or the waiting command was stopped. ## Usage ``` fly auth login [flags] ``` ## Options ``` --code string Finish a login that is waiting for approval, with the one-time code its approval page shows --email string Login email -h, --help help for login -i, --interactive Log in with an email and password interactively --otp string One time password --password string Login password ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly auth](/flyctl/cmd/fly_auth) - Manage authentication # fly auth logout Source: https://docs.fly.io/flyctl/cmd/fly_auth_logout Log the currently logged-in user out of the Fly platform. To continue interacting with Fly, the user will need to log in again. ## Usage ``` fly auth logout [flags] ``` ## Options ``` --force Remove the saved access token even if it could not be revoked -h, --help help for logout ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly auth](/flyctl/cmd/fly_auth) - Manage authentication # fly auth signup Source: https://docs.fly.io/flyctl/cmd/fly_auth_signup Creates a new fly account. The command opens the browser and sends the user to a form to provide appropriate credentials. ## Usage ``` fly auth signup [flags] ``` ## Options ``` -h, --help help for signup ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly auth](/flyctl/cmd/fly_auth) - Manage authentication # fly auth whoami Source: https://docs.fly.io/flyctl/cmd/fly_auth_whoami Show the currently authenticated user ## Usage ``` fly auth whoami [flags] ``` ## Options ``` -h, --help help for whoami -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 auth](/flyctl/cmd/fly_auth) - Manage authentication # fly certs Source: https://docs.fly.io/flyctl/cmd/fly_certs Manages the certificates associated with a deployed application. Certificates are created by associating a hostname/domain with the application. When Fly is then able to validate that hostname/domain, the platform gets certificates issued for the hostname/domain by Let's Encrypt. ## Usage ``` fly certs [command] [flags] ``` ## Available Commands * [add](/flyctl/cmd/fly_certs_add) - Add a certificate for an app * [check](/flyctl/cmd/fly_certs_check) - Show certificate and DNS status * [import](/flyctl/cmd/fly_certs_import) - Import a custom certificate * [list](/flyctl/cmd/fly_certs_list) - List certificates for an app * [remove](/flyctl/cmd/fly_certs_remove) - Removes a certificate from an app * [setup](/flyctl/cmd/fly_certs_setup) - Shows certificate setup instructions ## Options ``` -h, --help help for certs ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly certs add Source: https://docs.fly.io/flyctl/cmd/fly_certs_add Add a certificate for an application. Takes a hostname as a parameter for the certificate. ## Usage ``` fly certs add [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for add -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 certs](/flyctl/cmd/fly_certs) - Manage certificates # fly certs check Source: https://docs.fly.io/flyctl/cmd/fly_certs_check Shows detailed certificate information and checks the DNS configuration for the specified hostname. ## Usage ``` fly certs check [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for check -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 certs](/flyctl/cmd/fly_certs) - Manage certificates # fly certs import Source: https://docs.fly.io/flyctl/cmd/fly_certs_import Import a custom TLS certificate for a hostname. Upload your own certificate and private key in PEM format. Requires domain ownership verification via DNS before the certificate becomes active. ## Usage ``` fly certs import [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --fullchain string Path to certificate chain file (PEM format) -h, --help help for import -j, --json JSON output --private-key string Path to private key file (PEM format) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly certs](/flyctl/cmd/fly_certs) - Manage certificates # fly certs list Source: https://docs.fly.io/flyctl/cmd/fly_certs_list List the certificates associated with a deployed application. ## Usage ``` fly certs list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -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 certs](/flyctl/cmd/fly_certs) - Manage certificates # fly certs remove Source: https://docs.fly.io/flyctl/cmd/fly_certs_remove Removes a certificate from an application. Takes hostname as a parameter to locate the certificate. Use --custom to remove only the custom certificate while keeping ACME certificates. Use --acme to stop ACME certificate issuance while keeping custom certificates. ## Usage ``` fly certs remove [flags] ``` ## Options ``` --acme Stop ACME certificate issuance, keeping custom certificates -a, --app string Application name -c, --config string Path to application configuration file --custom Remove only the custom certificate, keeping ACME certificates -h, --help help for remove -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly certs](/flyctl/cmd/fly_certs) - Manage certificates # fly certs setup Source: https://docs.fly.io/flyctl/cmd/fly_certs_setup 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 setup [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for setup -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 certs](/flyctl/cmd/fly_certs) - Manage certificates # fly checks Source: https://docs.fly.io/flyctl/cmd/fly_checks Manage health checks ## Usage ``` fly checks [command] [flags] ``` ## Available Commands * [list](/flyctl/cmd/fly_checks_list) - List health checks ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for checks ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly checks list Source: https://docs.fly.io/flyctl/cmd/fly_checks_list List health checks ## Usage ``` fly checks list [flags] ``` ## Options ``` -a, --app string Application name --check-name string Filter checks by name -c, --config string Path to application configuration file -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 checks](/flyctl/cmd/fly_checks) - Manage health checks # fly config Source: https://docs.fly.io/flyctl/cmd/fly_config The CONFIG commands allow you to work with an application's configuration. ## Usage ``` fly config [command] [flags] ``` ## Available Commands * [env](/flyctl/cmd/fly_config_env) - Display an app's runtime environment variables * [save](/flyctl/cmd/fly_config_save) - Save an app's config file * [show](/flyctl/cmd/fly_config_show) - Show an app's configuration * [validate](/flyctl/cmd/fly_config_validate) - Validate an app's config file ## Options ``` -h, --help help for config ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly config env Source: https://docs.fly.io/flyctl/cmd/fly_config_env Display an app's runtime environment variables. It displays a section for secrets and another for config file defined environment variables. ## Usage ``` fly config env [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for env ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly config](/flyctl/cmd/fly_config) - Manage an app's configuration # fly config save Source: https://docs.fly.io/flyctl/cmd/fly_config_save Save an application's configuration locally. The configuration data is retrieved from the Fly service and saved in TOML format. ## Usage ``` fly config save [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for save --json Output the configuration in JSON format --yaml Output the configuration in YAML format -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly config](/flyctl/cmd/fly_config) - Manage an app's configuration # fly config show Source: https://docs.fly.io/flyctl/cmd/fly_config_show Show an application's configuration. The configuration is presented by default in JSON format. The configuration data is retrieved from the Fly service. ## Usage ``` fly config show [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for show --local Parse and show local fly.toml file instead of fetching from the Fly service --toml Show configuration in TOML format --yaml Show configuration in YAML format ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly config](/flyctl/cmd/fly_config) - Manage an app's configuration # fly config validate Source: https://docs.fly.io/flyctl/cmd/fly_config_validate Validates an application's config file against the Fly platform to ensure it is correct and meaningful to the platform. ## Usage ``` fly config validate [flags] ``` ## Options ``` -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 ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly config](/flyctl/cmd/fly_config) - Manage an app's configuration # fly console Source: https://docs.fly.io/flyctl/cmd/fly_console Run a console in a new or existing machine. The console command is specified by the `console_command` configuration field. By default, a new machine is created by default using the app's most recently deployed image. An existing machine can be used instead with --machine. ## Usage ``` fly console [flags] ``` ## Options ``` -a, --app string Application name --build-context-warn-size string Warn when the Docker build context is larger than this. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). Set to 0 to disable. Also set with FLY_BUILD_CONTEXT_WARN_SIZE. -C, --command string command to run on SSH session -c, --config string Path to application configuration file --container string Container to connect to --dockerfile string Path to a Dockerfile. Defaults to the Dockerfile in the working directory. --entrypoint string ENTRYPOINT replacement -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --file-literal stringArray Set of literals to write to the Machine, in the form of /path/inside/machine=VALUE pairs, where VALUE is the base64-encoded raw content. Can be specified multiple times. --file-local stringArray Set of files to write to the Machine, in the form of /path/inside/machine= pairs. Can be specified multiple times. --file-secret stringArray Set of secrets to write to the Machine, in the form of /path/inside/machine=SECRET pairs, where SECRET is the name of the secret. The content of the secret must be base64 encoded. Can be specified multiple times. -h, --help help for console --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) -i, --image string image to use (default: current release) --machine string Run the console in the existing machine with the specified ID -p, --port strings Publish ports, format: port[:machinePort][/protocol[:handler[:handler...]]]) 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 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') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values --volume strings Volume to mount, in the form of :/path/inside/machine[:] --wg Determines whether communication with remote builders are conducted over wireguard or plain internet(https) (default true) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly consul Source: https://docs.fly.io/flyctl/cmd/fly_consul Enable and manage Consul clusters ## Usage ``` fly consul [command] [flags] ``` ## Available Commands * [attach](/flyctl/cmd/fly_consul_attach) - Attach Consul cluster to an app * [detach](/flyctl/cmd/fly_consul_detach) - Detach Consul cluster from an app ## Options ``` -h, --help help for consul ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly consul attach Source: https://docs.fly.io/flyctl/cmd/fly_consul_attach Attach Consul cluster to an app, and setting the FLY\_CONSUL\_URL secret ## Usage ``` fly consul 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 environment variable name that will be added to the consuming app. (default "FLY_CONSUL_URL") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly consul](/flyctl/cmd/fly_consul) - Enable and manage Consul clusters # fly consul detach Source: https://docs.fly.io/flyctl/cmd/fly_consul_detach Detach Consul cluster from an app, and unsetting the FLY\_CONSUL\_URL secret ## Usage ``` fly consul detach [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for detach --variable-name string The secret name that will be removed from the app. (default "FLY_CONSUL_URL") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly consul](/flyctl/cmd/fly_consul) - Enable and manage Consul clusters # fly dashboard Source: https://docs.fly.io/flyctl/cmd/fly_dashboard Open web browser on Fly Web UI for this application ## Usage ``` fly dashboard [flags] ``` ## Available Commands * [metrics](/flyctl/cmd/fly_dashboard_metrics) - Open web browser on Fly Web UI for this app's metrics ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for dashboard ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly dashboard metrics Source: https://docs.fly.io/flyctl/cmd/fly_dashboard_metrics Open web browser on Fly Web UI for this application's metrics ## Usage ``` fly dashboard metrics [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -g, --grafana Open Grafana metrics dashboard directly -h, --help help for metrics ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly dashboard](/flyctl/cmd/fly_dashboard) - Open web browser on Fly Web UI for this app # fly deploy Source: https://docs.fly.io/flyctl/cmd/fly_deploy Deploy Fly applications from source or an image using a local or remote builder. To disable colorized output and show full Docker build output, set the environment variable NO\_COLOR=1. ## Usage ``` fly deploy [WORKING_DIRECTORY] [flags] ``` ## Options ``` -a, --app string Application name --build-arg stringArray Set of build time variables in the form of NAME=VALUE pairs. Can be specified multiple times. --build-context-warn-size string Warn when the Docker build context is larger than this. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). Set to 0 to disable. Also set with FLY_BUILD_CONTEXT_WARN_SIZE. --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. This option may set DOCKER_HOST environment variable for the build container if needed. --buildpacks-volume strings Mount host volume into the build container, in the form ':[:]'. - 'host path': Name of the volume or absolute directory path to mount. - 'target path': The path where the file or directory is available in the container. - 'options' (default "ro"): An optional comma separated list of mount options. - "ro", volume contents are read-only. - "rw", volume contents are readable and writeable. - "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") --depot-scope string The scope of the Depot builder's cache to use (org or app) (default "org") --detach Return immediately instead of monitoring deployment progress --dns-checks Perform DNS checks during deployment (default true) --dockerfile string Path to a Dockerfile. Defaults to the Dockerfile in the working directory. -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --exclude-machines strings Deploy to all machines except machines with these IDs. Multiple IDs can be specified with comma separated values or by providing the flag multiple times. --exclude-regions strings Deploy to all machines except machines in these regions. Multiple regions can be specified with comma separated values or by providing the flag multiple times. --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 Allocate a private IPv6 addresses --ha Create spare machines that increases app availability (default true) -h, --help help for deploy --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --https-failover Determines whether to failover to plain internet(https) communication with remote builders if wireguard fails (also --http-failover) (default true) --ignorefile string Path to a Docker ignore file. Defaults to the .dockerignore file in the working directory. -i, --image string The Docker image to deploy --image-label string Image label to use when tagging and pushing to the fly registry. Defaults to "deployment-{timestamp}". --label stringArray Add custom metadata to an image via docker labels --lease-timeout string Time duration to lease individual machines while running deployment. All machines are leased at the beginning and released at the end.The lease is refreshed periodically for this same time, which is why it is short.flyctl releases leases in most cases. (default "13s") --local-only Perform builds locally using the local docker daemon. The default is --remote-only. --max-concurrent int Maximum number of machines to operate on concurrently. (default 8) --max-unavailable float Max number of unavailable machines during rolling updates. A number between 0 and 1 means percent of total machines (default 0.33) --nixpacks Deploy using nixpacks to build the image --no-cache Do not use the build cache when building the image --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 --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. -s, --signal string Signal to stop the machine with for bluegreen strategy (default: SIGINT) --skip-release-command Do not run the release command during deployment. --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. --update-only Do not create Machines for new process groups --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values --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) -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly dig Source: https://docs.fly.io/flyctl/cmd/fly_dig Make DNS requests against Fly.io's internal DNS server. Valid types include AAAA and TXT (the two types our servers answer authoritatively), AAAA-NATIVE and TXT-NATIVE, which resolve with Go's resolver (they're slower, but may be useful if diagnosing a DNS bug) and A and CNAME (if you're using the server to test recursive lookups.) Note that this resolves names against the server for the current organization. You can set the organization with -o \; otherwise, the command uses the organization attached to the current app (you can pass an app in with -a \). ## Usage ``` fly dig [type] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for dig -o, --org string The target Fly.io organization -s, --short Just print the answers, not DNS record details ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly docs Source: https://docs.fly.io/flyctl/cmd/fly_docs View Fly documentation on the Fly.io website. This command will open a browser to view the content. ## Usage ``` fly docs [flags] ``` ## Options ``` -h, --help help for docs ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly doctor Source: https://docs.fly.io/flyctl/cmd/fly_doctor The DOCTOR command allows you to debug your Fly environment ## Usage ``` fly doctor [flags] ``` ## Available Commands * [diag](/flyctl/cmd/fly_doctor_diag) - Send diagnostic information about your applications back to Fly.io. ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for doctor -j, --json JSON output -o, --org string The name of the organization to use for WireGuard tests. (default "personal") -v, --verbose Print extra diagnostic information. ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly doctor diag Source: https://docs.fly.io/flyctl/cmd/fly_doctor_diag Send diagnostic information about your applications back to Fly.io, to help diagnose problems. This command will collect some local system information and a few files that you'd be sending us anyways in order to deploy, notably any Dockerfiles you might have associated with this application. ## Usage ``` fly doctor diag [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --force Send diagnostics even if we can't find your local Fly.io app -h, --help help for diag ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly doctor](/flyctl/cmd/fly_doctor) - The DOCTOR command allows you to debug your Fly environment # fly extensions Source: https://docs.fly.io/flyctl/cmd/fly_extensions Extensions are additional functionality that can be added to your Fly apps ## Usage ``` fly extensions [command] [flags] ``` ## Available Commands * [arcjet](/flyctl/cmd/fly_extensions_arcjet) - Provision and manage Arcjet * [kubernetes](/flyctl/cmd/fly_extensions_kubernetes) - Provision and manage Kubernetes clusters * [sentry](/flyctl/cmd/fly_extensions_sentry) - Setup a Sentry project for this app * [storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets * [vector](/flyctl/cmd/fly_extensions_vector) - Provision and manage Upstash Vector index * [wafris](/flyctl/cmd/fly_extensions_wafris) - Provision and manage Wafris WAFs (Web Application Firewalls) ## Options ``` -h, --help help for extensions ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly extensions arcjet Source: https://docs.fly.io/flyctl/cmd/fly_extensions_arcjet Provision and manage Arcjet ## Usage ``` fly extensions arcjet [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_extensions_arcjet_create) - Create an Arcjet site * [dashboard](/flyctl/cmd/fly_extensions_arcjet_dashboard) - Visit the Arcjet dashboard * [destroy](/flyctl/cmd/fly_extensions_arcjet_destroy) - Permanently destroy an Arcjet site * [list](/flyctl/cmd/fly_extensions_arcjet_list) - List your Arcjet sites * [status](/flyctl/cmd/fly_extensions_arcjet_status) - Show details about an Arcjet site setup ## Options ``` -h, --help help for arcjet ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps # fly extensions arcjet create Source: https://docs.fly.io/flyctl/cmd/fly_extensions_arcjet_create Create an Arcjet site ## Usage ``` fly extensions arcjet 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 application -o, --org string The target Fly.io organization -r, --region string The target region (see 'flyctl platform regions') -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions arcjet](/flyctl/cmd/fly_extensions_arcjet) - Provision and manage Arcjet # fly extensions arcjet dashboard Source: https://docs.fly.io/flyctl/cmd/fly_extensions_arcjet_dashboard Visit the Arcjet dashboard ## Usage ``` fly extensions arcjet dashboard [site_name] [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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions arcjet](/flyctl/cmd/fly_extensions_arcjet) - Provision and manage Arcjet # fly extensions arcjet destroy Source: https://docs.fly.io/flyctl/cmd/fly_extensions_arcjet_destroy Permanently destroy an Arcjet site ## Usage ``` fly extensions arcjet 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions arcjet](/flyctl/cmd/fly_extensions_arcjet) - Provision and manage Arcjet # fly extensions arcjet list Source: https://docs.fly.io/flyctl/cmd/fly_extensions_arcjet_list List your Arcjet sites ## Usage ``` fly extensions arcjet list [flags] ``` ## Options ``` -h, --help help for list -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions arcjet](/flyctl/cmd/fly_extensions_arcjet) - Provision and manage Arcjet # fly extensions arcjet status Source: https://docs.fly.io/flyctl/cmd/fly_extensions_arcjet_status Show details about an Arcjet site setup ## Usage ``` fly extensions arcjet 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions arcjet](/flyctl/cmd/fly_extensions_arcjet) - Provision and manage Arcjet # fly extensions kubernetes Source: https://docs.fly.io/flyctl/cmd/fly_extensions_kubernetes Provision and manage Kubernetes clusters ## Usage ``` fly extensions kubernetes [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_extensions_kubernetes_create) - Provision a Kubernetes cluster for an organization * [destroy](/flyctl/cmd/fly_extensions_kubernetes_destroy) - Permanently destroy a Kubernetes cluster * [list](/flyctl/cmd/fly_extensions_kubernetes_list) - List your Kubernetes clusters * [save-kubeconfig](/flyctl/cmd/fly_extensions_kubernetes_save-kubeconfig) - Save the kubeconfig file of your cluster ## Options ``` -h, --help help for kubernetes ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps # fly extensions kubernetes create Source: https://docs.fly.io/flyctl/cmd/fly_extensions_kubernetes_create Provision a Kubernetes cluster for an organization ## Usage ``` fly extensions kubernetes create [flags] ``` ## Options ``` -h, --help help for create -n, --name string The name of your cluster -o, --org string The target Fly.io organization --output string The output path to save the kubeconfig file -r, --region string The target region (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions kubernetes](/flyctl/cmd/fly_extensions_kubernetes) - Provision and manage Kubernetes clusters # fly extensions kubernetes destroy Source: https://docs.fly.io/flyctl/cmd/fly_extensions_kubernetes_destroy Permanently destroy a Kubernetes cluster ## Usage ``` fly extensions kubernetes destroy [name] [flags] ``` ## Options ``` -h, --help help for destroy -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions kubernetes](/flyctl/cmd/fly_extensions_kubernetes) - Provision and manage Kubernetes clusters # fly extensions kubernetes list Source: https://docs.fly.io/flyctl/cmd/fly_extensions_kubernetes_list List your Kubernetes clusters ## Usage ``` fly extensions kubernetes list [flags] ``` ## Options ``` -h, --help help for list ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions kubernetes](/flyctl/cmd/fly_extensions_kubernetes) - Provision and manage Kubernetes clusters # fly extensions kubernetes save-kubeconfig Source: https://docs.fly.io/flyctl/cmd/fly_extensions_kubernetes_save-kubeconfig Save the kubeconfig file of your cluster ## Usage ``` fly extensions kubernetes save-kubeconfig [cluster name] [flags] ``` ## Options ``` -h, --help help for save-kubeconfig --output string The output path to save the kubeconfig file ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions kubernetes](/flyctl/cmd/fly_extensions_kubernetes) - Provision and manage Kubernetes clusters # fly extensions sentry Source: https://docs.fly.io/flyctl/cmd/fly_extensions_sentry Setup a Sentry project for this app ## Usage ``` fly extensions sentry [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_extensions_sentry_create) - Provision a Sentry project for a Fly.io app * [dashboard](/flyctl/cmd/fly_extensions_sentry_dashboard) - View application errors in the Sentry dashboard * [destroy](/flyctl/cmd/fly_extensions_sentry_destroy) - Permanently destroy a Sentry project * [list](/flyctl/cmd/fly_extensions_sentry_list) - List your Sentry projects ## Options ``` -h, --help help for sentry ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps # fly extensions sentry create Source: https://docs.fly.io/flyctl/cmd/fly_extensions_sentry_create Provision a Sentry project for a Fly.io app ## Usage ``` fly extensions sentry create [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for create -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions sentry](/flyctl/cmd/fly_extensions_sentry) - Setup a Sentry project for this app # fly extensions sentry dashboard Source: https://docs.fly.io/flyctl/cmd/fly_extensions_sentry_dashboard View application errors in the Sentry dashboard ## Usage ``` fly extensions sentry dashboard [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for dashboard -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions sentry](/flyctl/cmd/fly_extensions_sentry) - Setup a Sentry project for this app # fly extensions sentry destroy Source: https://docs.fly.io/flyctl/cmd/fly_extensions_sentry_destroy Permanently destroy a Sentry project ## Usage ``` fly extensions sentry destroy [project-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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions sentry](/flyctl/cmd/fly_extensions_sentry) - Setup a Sentry project for this app # fly extensions sentry list Source: https://docs.fly.io/flyctl/cmd/fly_extensions_sentry_list List your Sentry projects ## Usage ``` fly extensions sentry list [flags] ``` ## Options ``` -h, --help help for list -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions sentry](/flyctl/cmd/fly_extensions_sentry) - Setup a Sentry project for this app # fly extensions storage Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage Provision and manage Tigris object storage buckets ## Usage ``` fly extensions storage [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_extensions_storage_create) - Provision a Tigris object storage bucket * [dashboard](/flyctl/cmd/fly_extensions_storage_dashboard) - Visit the Tigris dashboard * [destroy](/flyctl/cmd/fly_extensions_storage_destroy) - Permanently destroy a Tigris object storage bucket * [list](/flyctl/cmd/fly_extensions_storage_list) - List your Tigris object storage buckets * [status](/flyctl/cmd/fly_extensions_storage_status) - Show details about a Tigris storage bucket * [update](/flyctl/cmd/fly_extensions_storage_update) - Update an existing Tigris object storage bucket ## Options ``` -h, --help help for storage ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps # fly extensions storage create Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage_create Provision a Tigris object storage bucket ## Usage ``` fly extensions storage 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 bucket -o, --org string The target Fly.io organization -p, --public Objects in the bucket should be publicly accessible --shadow-access-key string Shadow bucket access key --shadow-endpoint string Shadow bucket endpoint --shadow-name string Shadow bucket name --shadow-region string Shadow bucket region --shadow-secret-key string Shadow bucket secret key --shadow-write-through Write objects through to the shadow bucket -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets # fly extensions storage dashboard Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage_dashboard Visit the Tigris dashboard ## Usage ``` fly extensions storage dashboard [bucket_name] [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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets # fly extensions storage destroy Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage_destroy Permanently destroy a Tigris object storage bucket ## Usage ``` fly extensions storage destroy [storage-bucket-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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets # fly extensions storage list Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage_list List your Tigris object storage buckets ## Usage ``` fly extensions storage list [flags] ``` ## Options ``` -h, --help help for list -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets # fly extensions storage status Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage_status Show details about a Tigris storage bucket ## Usage ``` fly extensions storage 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets # fly extensions storage update Source: https://docs.fly.io/flyctl/cmd/fly_extensions_storage_update Update an existing Tigris object storage bucket ## Usage ``` fly extensions storage update [flags] ``` ## Options ``` -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 -p, --public Objects in the bucket should be publicly accessible --shadow-access-key string Shadow bucket access key --shadow-endpoint string Shadow bucket endpoint --shadow-name string Shadow bucket name --shadow-region string Shadow bucket region --shadow-secret-key string Shadow bucket secret key --shadow-write-through Write objects through to the shadow bucket -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions storage](/flyctl/cmd/fly_extensions_storage) - Provision and manage Tigris object storage buckets # fly extensions vector Source: https://docs.fly.io/flyctl/cmd/fly_extensions_vector Provision and manage Upstash Vector index ## Usage ``` fly extensions vector [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_extensions_vector_create) - Provision a Upstash Vector index * [dashboard](/flyctl/cmd/fly_extensions_vector_dashboard) - Visit the Upstash Vector dashboard on the Upstash web console * [destroy](/flyctl/cmd/fly_extensions_vector_destroy) - Permanently destroy an Upstash Vector index * [list](/flyctl/cmd/fly_extensions_vector_list) - List your Upstash Vector index * [status](/flyctl/cmd/fly_extensions_vector_status) - Show details about an Upstash Vector index ## Options ``` -h, --help help for vector ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps # fly extensions vector create Source: https://docs.fly.io/flyctl/cmd/fly_extensions_vector_create Provision a Upstash Vector index ## Usage ``` fly extensions vector 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions vector](/flyctl/cmd/fly_extensions_vector) - Provision and manage Upstash Vector index # fly extensions vector dashboard Source: https://docs.fly.io/flyctl/cmd/fly_extensions_vector_dashboard Visit the Upstash Vector dashboard on the Upstash web console ## Usage ``` fly extensions vector 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions vector](/flyctl/cmd/fly_extensions_vector) - Provision and manage Upstash Vector index # fly extensions vector destroy Source: https://docs.fly.io/flyctl/cmd/fly_extensions_vector_destroy Permanently destroy an Upstash Vector index ## Usage ``` fly extensions vector 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions vector](/flyctl/cmd/fly_extensions_vector) - Provision and manage Upstash Vector index # fly extensions vector list Source: https://docs.fly.io/flyctl/cmd/fly_extensions_vector_list List your Upstash Vector index ## Usage ``` fly extensions vector list [flags] ``` ## Options ``` -h, --help help for list -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions vector](/flyctl/cmd/fly_extensions_vector) - Provision and manage Upstash Vector index # fly extensions vector status Source: https://docs.fly.io/flyctl/cmd/fly_extensions_vector_status Show details about an Upstash Vector index ## Usage ``` fly extensions vector 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions vector](/flyctl/cmd/fly_extensions_vector) - Provision and manage Upstash Vector index # fly extensions wafris Source: https://docs.fly.io/flyctl/cmd/fly_extensions_wafris Provision and manage Wafris WAFs (Web Application Firewalls) ## Usage ``` fly extensions wafris [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_extensions_wafris_create) - Provision a Wafris WAF * [dashboard](/flyctl/cmd/fly_extensions_wafris_dashboard) - Visit the Wafris dashboard * [destroy](/flyctl/cmd/fly_extensions_wafris_destroy) - Permanently destroy a Wafris WAF ## Options ``` -h, --help help for wafris ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps # fly extensions wafris create Source: https://docs.fly.io/flyctl/cmd/fly_extensions_wafris_create Provision a Wafris WAF ## Usage ``` fly extensions wafris 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 WAF -o, --org string The target Fly.io organization -r, --region string The target region (see 'flyctl platform regions') -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions wafris](/flyctl/cmd/fly_extensions_wafris) - Provision and manage Wafris WAFs (Web Application Firewalls) # fly extensions wafris dashboard Source: https://docs.fly.io/flyctl/cmd/fly_extensions_wafris_dashboard Visit the Wafris dashboard ## Usage ``` fly extensions wafris dashboard [firewall_name] [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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions wafris](/flyctl/cmd/fly_extensions_wafris) - Provision and manage Wafris WAFs (Web Application Firewalls) # fly extensions wafris destroy Source: https://docs.fly.io/flyctl/cmd/fly_extensions_wafris_destroy Permanently destroy a Wafris WAF ## Usage ``` fly extensions wafris 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly extensions wafris](/flyctl/cmd/fly_extensions_wafris) - Provision and manage Wafris WAFs (Web Application Firewalls) # fly Source: https://docs.fly.io/flyctl/cmd/fly_help This is flyctl, the Fly.io command line interface. ## Usage ``` fly [flags] ``` ## Available Commands * [agent](/flyctl/cmd/fly_agent) - Commands that manage the Fly agent, a background process that manages flyctl wireguard connections * [apps](/flyctl/cmd/fly_apps) - Manage apps. * [auth](/flyctl/cmd/fly_auth) - Manage authentication * [certs](/flyctl/cmd/fly_certs) - Manage certificates * [checks](/flyctl/cmd/fly_checks) - Manage health checks * [config](/flyctl/cmd/fly_config) - Manage an app's configuration * [console](/flyctl/cmd/fly_console) - Run a console in a new or existing machine * [consul](/flyctl/cmd/fly_consul) - Enable and manage Consul clusters * [dashboard](/flyctl/cmd/fly_dashboard) - Open web browser on Fly Web UI for this app * [deploy](/flyctl/cmd/fly_deploy) - Deploy Fly applications * [dig](/flyctl/cmd/fly_dig) - Make DNS requests against Fly.io's internal DNS server * [docs](/flyctl/cmd/fly_docs) - View Fly documentation * [doctor](/flyctl/cmd/fly_doctor) - The DOCTOR command allows you to debug your Fly environment * [extensions](/flyctl/cmd/fly_extensions) - Extensions are additional functionality that can be added to your Fly apps * [image](/flyctl/cmd/fly_image) - Manage app image * [incidents](/flyctl/cmd/fly_incidents) - Show incidents * [ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps * [jobs](/flyctl/cmd/fly_jobs) - Show jobs at Fly.io * [launch](/flyctl/cmd/fly_launch) - Create and configure a new app from source code or a Docker image * [logs](/flyctl/cmd/fly_logs) - View app logs * [machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. * [mcp](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. * [mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. * [orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations * [ping](/flyctl/cmd/fly_ping) - Test connectivity with ICMP ping messages * [platform](/flyctl/cmd/fly_platform) - Fly platform information * [postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands * [proxy](/flyctl/cmd/fly_proxy) - Proxies connections to a Fly Machine. * [redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com * [releases](/flyctl/cmd/fly_releases) - List app releases * [scale](/flyctl/cmd/fly_scale) - Scale app resources * [secrets](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. * [services](/flyctl/cmd/fly_services) - Show the application's services * [settings](/flyctl/cmd/fly_settings) - Manage flyctl settings * [sftp](/flyctl/cmd/fly_sftp) - Get or put files from a remote VM. * [ssh](/flyctl/cmd/fly_ssh) - Use SSH to log into or run commands on Machines * [status](/flyctl/cmd/fly_status) - Show app status * [storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets * [synthetics](/flyctl/cmd/fly_synthetics) - Synthetic monitoring * [tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens * [version](/flyctl/cmd/fly_version) - Show version information for the flyctl command * [volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. * [wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections ## Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces -h, --help help for fly --verbose Verbose output ``` ## See Also # fly image Source: https://docs.fly.io/flyctl/cmd/fly_image Manage app image ## Usage ``` fly image [command] [flags] ``` ## Available Commands * [show](/flyctl/cmd/fly_image_show) - Show image details. * [update](/flyctl/cmd/fly_image_update) - Updates the app's image to the latest available version. ## Options ``` -h, --help help for image ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly image show Source: https://docs.fly.io/flyctl/cmd/fly_image_show Show image details. ## Usage ``` fly image show [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for show -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 image](/flyctl/cmd/fly_image) - Manage app image # fly image update Source: https://docs.fly.io/flyctl/cmd/fly_image_update Update the app's image to the latest available version. The update will perform a rolling restart against each Machine, which may result in a brief service disruption. ## Usage ``` fly image update [flags] ``` ## Options ``` -a, --app string Application name -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 between VM updates. -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly image](/flyctl/cmd/fly_image) - Manage app image # fly incidents Source: https://docs.fly.io/flyctl/cmd/fly_incidents Show incidents that may be affecting your organization. ## Usage ``` fly incidents [command] [flags] ``` ## Available Commands * [hosts](/flyctl/cmd/fly_incidents_hosts) - Show hosts' incidents * [list](/flyctl/cmd/fly_incidents_list) - List active incidents. ## Options ``` -h, --help help for incidents ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly incidents hosts Source: https://docs.fly.io/flyctl/cmd/fly_incidents_hosts Show hosts' incidents affecting applications ## Usage ``` fly incidents hosts [command] [flags] ``` ## Available Commands * [list](/flyctl/cmd/fly_incidents_hosts_list) - List hosts' issues affecting the application ## Options ``` -h, --help help for hosts ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly incidents](/flyctl/cmd/fly_incidents) - Show incidents # fly incidents hosts list Source: https://docs.fly.io/flyctl/cmd/fly_incidents_hosts_list List hosts' issues affecting the application ## Usage ``` fly incidents hosts list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -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 incidents hosts](/flyctl/cmd/fly_incidents_hosts) - Show hosts' incidents # fly incidents list Source: https://docs.fly.io/flyctl/cmd/fly_incidents_list List the active incidents that may be affecting your apps or deployments. ## Usage ``` fly incidents list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for list -j, --json JSON output -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 incidents](/flyctl/cmd/fly_incidents) - Show incidents # fly ips Source: https://docs.fly.io/flyctl/cmd/fly_ips Commands for managing IP addresses associated with an application ## Usage ``` fly ips [command] [flags] ``` ## Available Commands * [allocate](/flyctl/cmd/fly_ips_allocate) - Allocate recommended IP addresses * [allocate-egress](/flyctl/cmd/fly_ips_allocate-egress) - Allocate app-scoped egress IPs * [allocate-v4](/flyctl/cmd/fly_ips_allocate-v4) - Allocate an IPv4 address * [allocate-v6](/flyctl/cmd/fly_ips_allocate-v6) - Allocate an IPv6 address * [list](/flyctl/cmd/fly_ips_list) - List allocated IP addresses * [private](/flyctl/cmd/fly_ips_private) - List instances private IP addresses * [release](/flyctl/cmd/fly_ips_release) - Release ingress IP addresses * [release-egress](/flyctl/cmd/fly_ips_release-egress) - Release egress IP addresses ## Options ``` -h, --help help for ips ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly ips allocate Source: https://docs.fly.io/flyctl/cmd/fly_ips_allocate Allocate recommended IP addresses for the application ## Usage ``` fly ips allocate [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for allocate -r, --region string The target region (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips allocate-egress Source: https://docs.fly.io/flyctl/cmd/fly_ips_allocate-egress Allocates a pair of egress IP addresses for an app ## Usage ``` fly ips allocate-egress [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for allocate-egress -r, --region string The target region (see 'flyctl platform regions') -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips allocate-v4 Source: https://docs.fly.io/flyctl/cmd/fly_ips_allocate-v4 Allocates an IPv4 address to the application ## Usage ``` fly ips allocate-v4 [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for allocate-v4 -r, --region string The target region (see 'flyctl platform regions') --shared Allocates a shared IPv4 -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips allocate-v6 Source: https://docs.fly.io/flyctl/cmd/fly_ips_allocate-v6 Allocates an IPv6 address to the application ## Usage ``` fly ips allocate-v6 [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for allocate-v6 --network string Target network name for a Flycast private IPv6 address -o, --org string The target Fly.io organization --private Allocate a private IPv6 address -r, --region string The target region (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips list Source: https://docs.fly.io/flyctl/cmd/fly_ips_list Lists the IP addresses allocated to the application ## Usage ``` fly ips list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -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 ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips private Source: https://docs.fly.io/flyctl/cmd/fly_ips_private List instances private IP addresses, accessible from within the Fly network ## Usage ``` fly ips private [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for private -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 ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips release Source: https://docs.fly.io/flyctl/cmd/fly_ips_release Releases one or more ingress IP addresses from the application ## Usage ``` fly ips release [flags] ADDRESS ADDRESS ... ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for release ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly ips release-egress Source: https://docs.fly.io/flyctl/cmd/fly_ips_release-egress Releases one or more egress IP addresses from the application ## Usage ``` fly ips release-egress [flags] ADDRESS ADDRESS ... ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for release-egress ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ips](/flyctl/cmd/fly_ips) - Manage IP addresses for apps # fly jobs Source: https://docs.fly.io/flyctl/cmd/fly_jobs Show jobs at Fly.io, including maybe ones you should apply to ## Usage ``` fly jobs [flags] ``` ## Available Commands * [open](/flyctl/cmd/fly_jobs_open) - Open fly.io/jobs ## Options ``` -h, --help help for jobs ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly jobs open Source: https://docs.fly.io/flyctl/cmd/fly_jobs_open Open browser to [https://fly.io/jobs/](https://fly.io/jobs/) ## Usage ``` fly jobs open [flags] ``` ## Options ``` -h, --help help for open ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly jobs](/flyctl/cmd/fly_jobs) - Show jobs at Fly.io # fly launch Source: https://docs.fly.io/flyctl/cmd/fly_launch Create and configure a new app from source code or a Docker image. Options passed after double dashes ("--") will be passed to the language specific scanner/dockerfile generator. ## Usage ``` fly launch [flags] ``` ## Options ``` --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") --build-arg stringArray Set of build time variables in the form of NAME=VALUE pairs. Can be specified multiple times. --build-context-warn-size string Warn when the Docker build context is larger than this. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). Set to 0 to disable. Also set with FLY_BUILD_CONTEXT_WARN_SIZE. --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. This option may set DOCKER_HOST environment variable for the build container if needed. --buildpacks-volume strings Mount host volume into the build container, in the form ':[:]'. - 'host path': Name of the volume or absolute directory path to mount. - 'target path': The path where the file or directory is available in the container. - 'options' (default "ro"): An optional comma separated list of mount options. - "ro", volume contents are read-only. - "rw", volume contents are readable and writeable. - "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 CMD. --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") --detach Return immediately instead of monitoring deployment progress --dns-checks Perform DNS checks during deployment (default true) --dockerfile string Path to a Dockerfile. Defaults to the Dockerfile in the working directory. --dockerignore-from-gitignore If a .dockerignore does not exist, create one from .gitignore files -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --exclude-machines strings Deploy to all machines except machines with these IDs. Multiple IDs can be specified with comma separated values or by providing the flag multiple times. --exclude-regions strings Deploy to all machines except machines in these regions. Multiple regions can be specified with comma separated values or by providing the flag multiple times. --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 Allocate a private IPv6 addresses --from string A github repo URL to use as a template for the new app --generate-name Always generate a name for the app, without prompting --ha Create spare machines that increases app availability (default true) -h, --help help for launch --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --https-failover Determines whether to failover to plain internet(https) communication with remote builders if wireguard fails (also --http-failover) (default true) --ignorefile string Path to a Docker ignore file. Defaults to the .dockerignore file in the working directory. -i, --image string The Docker image to deploy --image-label string Image label to use when tagging and pushing to the fly registry. Defaults to "deployment-{timestamp}". --internal-port int Set internal_port for all services in the generated fly.toml (default -1) --into string Destination directory for github repo specified with --from --json Generate configuration in JSON format --label stringArray Add custom metadata to an image via docker labels --lease-timeout string Time duration to lease individual machines while running deployment. All machines are leased at the beginning and released at the end.The lease is refreshed periodically for this same time, which is why it is short.flyctl releases leases in most cases. (default "13s") --local-only Perform builds locally using the local docker daemon. The default is --remote-only. --max-concurrent int Maximum number of machines to operate on concurrently. (default 8) --max-unavailable float Max number of unavailable machines during rolling updates. A number between 0 and 1 means percent of total machines (default 0.33) --name string Name of the new app --nixpacks Deploy using nixpacks to build the image --no-cache Do not use the build cache when building the image --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 --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. -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 -r, --region string The target region (see 'flyctl platform regions') --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. --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --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) --yaml Generate configuration in YAML format -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly logs Source: https://docs.fly.io/flyctl/cmd/fly_logs View application logs as generated by the application running on the Fly platform. Logs can be filtered to a specific machine using the --machine/-m flag or to all machines running in a specific region using the --region/-r flag. By default logs are continually streamed until the command is aborted. Use --no-tail to only fetch the logs in the buffer. ## Usage ``` fly logs [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for logs -j, --json JSON output -m, --machine string Filter by machine ID -n, --no-tail Do not continually stream logs -r, --region string The target region (see 'flyctl platform regions') -s, --select Select a machine from a list ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly machine Source: https://docs.fly.io/flyctl/cmd/fly_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. ## Usage ``` fly machine [command] [flags] ``` ## Available Commands * [api-proxy](/flyctl/cmd/fly_machine_api-proxy) - Establish a proxy to the Machine API through a Wireguard tunnel for local connections * [clone](/flyctl/cmd/fly_machine_clone) - Clone a Fly Machine * [cordon](/flyctl/cmd/fly_machine_cordon) - Deactivate all services on a machine * [create](/flyctl/cmd/fly_machine_create) - Create, but don't start, a machine * [destroy](/flyctl/cmd/fly_machine_destroy) - Destroy Fly machines * [exec](/flyctl/cmd/fly_machine_exec) - Execute a command on a machine * [kill](/flyctl/cmd/fly_machine_kill) - Kill (SIGKILL) a Fly machine * [leases](/flyctl/cmd/fly_machine_leases) - Manage machine leases * [list](/flyctl/cmd/fly_machine_list) - List Fly machines * [place](/flyctl/cmd/fly_machine_place) - Simulate Machine placements * [restart](/flyctl/cmd/fly_machine_restart) - Restart one or more Fly machines * [run](/flyctl/cmd/fly_machine_run) - Run a machine * [start](/flyctl/cmd/fly_machine_start) - Start one or more Fly machines * [status](/flyctl/cmd/fly_machine_status) - Show current status of a running machine * [stop](/flyctl/cmd/fly_machine_stop) - Stop one or more Fly machines * [suspend](/flyctl/cmd/fly_machine_suspend) - Suspend one or more Fly machines * [uncordon](/flyctl/cmd/fly_machine_uncordon) - Reactivate all services on a machine * [update](/flyctl/cmd/fly_machine_update) - Update a machine * [wait](/flyctl/cmd/fly_machine_wait) - Wait for a machine to reach a state ## Options ``` -h, --help help for machine ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly machine api-proxy Source: https://docs.fly.io/flyctl/cmd/fly_machine_api-proxy Establish a proxy to the Machine API through a Wireguard tunnel for local connections ## Usage ``` fly machine api-proxy [flags] ``` ## Options ``` -h, --help help for api-proxy -o, --org string The target Fly.io organization -q, --quiet Don't print progress indicators for WireGuard ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine clone Source: https://docs.fly.io/flyctl/cmd/fly_machine_clone Clone a Fly Machine. The new Machine will be a copy of the specified Machine. If the original Machine has a volume, then a new empty volume will be created and attached to the new Machine. ## Usage ``` fly machine clone [machine_id] [flags] ``` ## Options ``` -a, --app string Application name --attach-volume string Existing volume to attach to the new Machine in the form of [:/path/inside/machine] --clear-auto-destroy Disable auto destroy setting on the new Machine --clear-cmd Set empty CMD on the new Machine so it uses default CMD for the image -c, --config string Path to application configuration file --detach Return immediately instead of monitoring deployment progress --from-snapshot string Clone attached volumes and restore from snapshot, use 'last' for most recent snapshot. The default is an empty volume. -h, --help help for clone --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --name string Optional name for the new Machine --override-cmd string Set CMD on the new Machine to this value -r, --region string The target region (see 'flyctl platform regions') --standby-for strings Comma separated list of Machine IDs to watch for. You can use '--standby-for=source' to create a standby for the cloned Machine. --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values --volume-requires-unique-zone Require volume to be placed in separate hardware zone from existing volumes. Default true. (default true) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine cordon Source: https://docs.fly.io/flyctl/cmd/fly_machine_cordon Deactivate all services on a machine ## Usage ``` fly machine cordon [...] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for cordon ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine create Source: https://docs.fly.io/flyctl/cmd/fly_machine_create Create, but don't start, a machine ## Usage ``` fly machine create [command] [flags] ``` ## Options ``` -a, --app string Application name --autostart Automatically start a stopped Machine when a network request is received (default true) --autostop string[="stop"] Automatically stop a Machine when there are no network requests for it. Options include 'off', 'stop', and 'suspend'. (default "off") --build-context-warn-size string Warn when the Docker build context is larger than this. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). Set to 0 to disable. Also set with FLY_BUILD_CONTEXT_WARN_SIZE. --build-depot Build your image with depot.dev --build-nixpacks Build your image with nixpacks --cachedrive-size string Cache drive size in MB. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 10gb). Set to 0 to disable. -c, --config string Path to application configuration file --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. -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --file-literal stringArray Set of literals to write to the Machine, in the form of /path/inside/machine=VALUE pairs, where VALUE is the base64-encoded raw content. Can be specified multiple times. --file-local stringArray Set of files to write to the Machine, in the form of /path/inside/machine= pairs. Can be specified multiple times. --file-secret stringArray Set of secrets to write to the Machine, in the form of /path/inside/machine=SECRET pairs, where SECRET is the name of the secret. The content of the secret must be base64 encoded. Can be specified multiple times. -h, --help help for create --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --id string Machine ID, if previously known --kernel-arg stringArray A list of kernel arguments to provide to the init. Can be specified multiple times. --machine-config string Read Machine config from a JSON file or string (use --config for app configuration) -m, --metadata stringArray Metadata in the form of NAME=VALUE pairs. Can be specified multiple times. -n, --name string Machine name. Will be generated if omitted. --org string The organization that will own the app -p, --port strings The external ports and handlers for services, in the format: port[:machinePort][/protocol[:handler[:handler...]]]) For example: --port 80/tcp --port 443:80/tcp:http:tls --port 5432/tcp:pg_tls To remove a port mapping use '-' as handler. For example: --port 80/tcp:- -r, --region string The target region (see 'flyctl platform regions') --restart string Set the restart policy for a Machine. Options include 'no', 'always', and 'on-failure'. Default is 'on-failure' for Machines created by 'fly deploy' and Machines with a schedule. Default is 'always' for Machines created by 'fly m run'. --rm Automatically remove the Machine when it exits. Sets the restart-policy to 'never' if not otherwise specified. --rootfs-persist string Whether to persist the root filesystem across restarts. Options include 'never', 'always', and 'restart'. --rootfs-size string Root filesystem size in GB. Accepts a plain number (in GB) or a human-readable size (e.g. 2gb, 5gb). Uses an overlayfs to allow the root filesystem to exceed its default size. Set to 0 to unset. --schedule string Schedule a Machine run at hourly, daily, weekly and monthly intervals --skip-dns-registration Do not register the machine's 6PN IP with the internal DNS system --standby-for strings For Machines without services, a comma separated list of Machine IDs to act as standby for. --swap-size string Swap size in MB. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). --use-zstd Enable zstd compression for the image --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --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[:] ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine destroy Source: https://docs.fly.io/flyctl/cmd/fly_machine_destroy Destroy one or more Fly machines. This command requires a machine to be in a stopped or suspended state unless the force flag is used. ## Usage ``` fly machine destroy [flags] ID ID ... ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -f, --force force kill machine regardless of current state -h, --help help for destroy -i, --image string remove all machines with the specified image hash ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine exec Source: https://docs.fly.io/flyctl/cmd/fly_machine_exec Execute a command on a machine ## Usage ``` fly machine exec [machine-id] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --container string Container to run the command in -h, --help help for exec -j, --json JSON output --no-container Run the command on the machine itself rather than in one of its containers --timeout int Timeout in seconds ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine kill Source: https://docs.fly.io/flyctl/cmd/fly_machine_kill Kill (SIGKILL) a Fly machine ## Usage ``` fly machine kill [id] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for kill ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine leases Source: https://docs.fly.io/flyctl/cmd/fly_machine_leases Manage machine leases ## Usage ``` fly machine leases [command] [flags] ``` ## Available Commands * [clear](/flyctl/cmd/fly_machine_leases_clear) - Clear machine leases * [view](/flyctl/cmd/fly_machine_leases_view) - View machine leases ## Options ``` -h, --help help for leases ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine leases clear Source: https://docs.fly.io/flyctl/cmd/fly_machine_leases_clear Clear machine leases ## Usage ``` fly machine leases clear [machine-id] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for clear ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine leases](/flyctl/cmd/fly_machine_leases) - Manage machine leases # fly machine leases view Source: https://docs.fly.io/flyctl/cmd/fly_machine_leases_view View machine leases ## Usage ``` fly machine leases view [machine-id] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for view -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 machine leases](/flyctl/cmd/fly_machine_leases) - Manage machine leases # fly machine list Source: https://docs.fly.io/flyctl/cmd/fly_machine_list List Fly machines ## Usage ``` fly machine list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for list -j, --json JSON output -q, --quiet Only list machine ids ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine place Source: https://docs.fly.io/flyctl/cmd/fly_machine_place 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') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --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](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine restart Source: https://docs.fly.io/flyctl/cmd/fly_machine_restart Restart one or more Fly machines ## Usage ``` fly machine restart [...] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --force Force stop the machine(s) -h, --help help for restart -s, --signal string Signal to stop the machine with (default: SIGINT) --skip-health-checks Restarts app without waiting for health checks. --time int Seconds to wait before killing the machine ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine run Source: https://docs.fly.io/flyctl/cmd/fly_machine_run Run a machine ## Usage ``` fly machine run [command] [flags] ``` ## Options ``` -a, --app string Application name --autostart Automatically start a stopped Machine when a network request is received (default true) --autostop string[="stop"] Automatically stop a Machine when there are no network requests for it. Options include 'off', 'stop', and 'suspend'. (default "off") --build-context-warn-size string Warn when the Docker build context is larger than this. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). Set to 0 to disable. Also set with FLY_BUILD_CONTEXT_WARN_SIZE. --build-depot Build your image with depot.dev --build-nixpacks Build your image with nixpacks --cachedrive-size string Cache drive size in MB. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 10gb). Set to 0 to disable. --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. -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --file-literal stringArray Set of literals to write to the Machine, in the form of /path/inside/machine=VALUE pairs, where VALUE is the base64-encoded raw content. Can be specified multiple times. --file-local stringArray Set of files to write to the Machine, in the form of /path/inside/machine= pairs. Can be specified multiple times. --file-secret stringArray Set of secrets to write to the Machine, in the form of /path/inside/machine=SECRET pairs, where SECRET is the name of the secret. The content of the secret must be base64 encoded. Can be specified multiple times. -h, --help help for run --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --id string Machine ID, if previously known --kernel-arg stringArray A list of kernel arguments to provide to the init. Can be specified multiple times. --machine-config string Read Machine config from a JSON file or string (use --config for app configuration) -m, --metadata stringArray Metadata in the form of NAME=VALUE pairs. Can be specified multiple times. -n, --name string Machine name. Will be generated if omitted. --org string The organization that will own the app -p, --port strings The external ports and handlers for services, in the format: port[:machinePort][/protocol[:handler[:handler...]]]) For example: --port 80/tcp --port 443:80/tcp:http:tls --port 5432/tcp:pg_tls To remove a port mapping use '-' as handler. For example: --port 80/tcp:- -r, --region string The target region (see 'flyctl platform regions') --restart string Set the restart policy for a Machine. Options include 'no', 'always', and 'on-failure'. Default is 'on-failure' for Machines created by 'fly deploy' and Machines with a schedule. Default is 'always' for Machines created by 'fly m run'. --rm Automatically remove the Machine when it exits. Sets the restart-policy to 'never' if not otherwise specified. --rootfs-persist string Whether to persist the root filesystem across restarts. Options include 'never', 'always', and 'restart'. --rootfs-size string Root filesystem size in GB. Accepts a plain number (in GB) or a human-readable size (e.g. 2gb, 5gb). Uses an overlayfs to allow the root filesystem to exceed its default size. Set to 0 to unset. --schedule string Schedule a Machine run at hourly, daily, weekly and monthly intervals --shell Open a shell on the Machine once created (implies --it --rm). If no app is specified, an app for interactive shells is created or reused. The Machine is destroyed when the shell exits; the app is retained for future shells. See also --command and --user. --skip-dns-registration Do not register the machine's 6PN IP with the internal DNS system --standby-for strings For Machines without services, a comma separated list of Machine IDs to act as standby for. --swap-size string Swap size in MB. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). --use-zstd Enable zstd compression for the image --user string Used with --shell. The username, if we're shelling into the Machine now. (default "root") --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --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[:] --wg Determines whether communication with remote builders are conducted over wireguard or plain internet(https) (default true) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine start Source: https://docs.fly.io/flyctl/cmd/fly_machine_start Start one or more Fly machines ## Usage ``` fly machine start [...] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for start ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine status Source: https://docs.fly.io/flyctl/cmd/fly_machine_status Show current status of a running machine ## Usage ``` fly machine status [id] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -d, --display-config Display the machine config as JSON -h, --help help for status ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine stop Source: https://docs.fly.io/flyctl/cmd/fly_machine_stop Stop one or more Fly machines ## Usage ``` fly machine stop [...] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for stop -s, --signal string Signal to stop the machine with (default: SIGINT) --timeout int Seconds to wait before sending SIGKILL to the machine -w, --wait-timeout duration Time duration to wait for individual machines to transition states and become stopped. ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine suspend Source: https://docs.fly.io/flyctl/cmd/fly_machine_suspend Suspend one or more Fly machines ## Usage ``` fly machine suspend [...] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for suspend -w, --wait-timeout duration Duration to wait for individual Machines to be suspended. ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine uncordon Source: https://docs.fly.io/flyctl/cmd/fly_machine_uncordon Reactivate all services on a machine ## Usage ``` fly machine uncordon [...] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for uncordon ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine update Source: https://docs.fly.io/flyctl/cmd/fly_machine_update Update a machine ## Usage ``` fly machine update [machine_id] [flags] ``` ## Options ``` -a, --app string Application name --autostart Automatically start a stopped Machine when a network request is received (default true) --autostop string[="stop"] Automatically stop a Machine when there are no network requests for it. Options include 'off', 'stop', and 'suspend'. (default "off") --build-context-warn-size string Warn when the Docker build context is larger than this. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). Set to 0 to disable. Also set with FLY_BUILD_CONTEXT_WARN_SIZE. --build-depot Build your image with depot.dev --build-nixpacks Build your image with nixpacks --buildkit Deploy using buildkit-based remote builder --cachedrive-size string Cache drive size in MB. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 10gb). Set to 0 to disable. -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. -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --file-literal stringArray Set of literals to write to the Machine, in the form of /path/inside/machine=VALUE pairs, where VALUE is the base64-encoded raw content. Can be specified multiple times. --file-local stringArray Set of files to write to the Machine, in the form of /path/inside/machine= pairs. Can be specified multiple times. --file-secret stringArray Set of secrets to write to the Machine, in the form of /path/inside/machine=SECRET pairs, where SECRET is the name of the secret. The content of the secret must be base64 encoded. Can be specified multiple times. -h, --help help for update --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) -i, --image string The Docker image to deploy --kernel-arg stringArray A list of kernel arguments to provide to the init. Can be specified multiple times. --machine-config string Read Machine config from a JSON file or string (use --config for app configuration) -m, --metadata stringArray Metadata in the form of NAME=VALUE pairs. Can be specified multiple times. --mount-point string New volume mount point -p, --port strings The external ports and handlers for services, in the format: port[:machinePort][/protocol[:handler[:handler...]]]) For example: --port 80/tcp --port 443:80/tcp:http:tls --port 5432/tcp:pg_tls To remove a port mapping use '-' as handler. For example: --port 80/tcp:- --restart string Set the restart policy for a Machine. Options include 'no', 'always', and 'on-failure'. Default is 'on-failure' for Machines created by 'fly deploy' and Machines with a schedule. Default is 'always' for Machines created by 'fly m run'. --rootfs-persist string Whether to persist the root filesystem across restarts. Options include 'never', 'always', and 'restart'. --rootfs-size string Root filesystem size in GB. Accepts a plain number (in GB) or a human-readable size (e.g. 2gb, 5gb). Uses an overlayfs to allow the root filesystem to exceed its default size. Set to 0 to unset. --schedule string Schedule a Machine run at hourly, daily, weekly and monthly intervals --skip-dns-registration Do not register the machine's 6PN IP with the internal DNS system --skip-health-checks Updates machine without waiting for health checks. --skip-start Updates machine without starting it. --standby-for strings For Machines without services, a comma separated list of Machine IDs to act as standby for. --swap-size string Swap size in MB. Accepts a plain number (in MB) or a human-readable size (e.g. 512mb, 1gb). --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values --wait-timeout int Seconds to wait for individual machines to transition states and become healthy. (default 300) (default 300) -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly machine wait Source: https://docs.fly.io/flyctl/cmd/fly_machine_wait Wait for a machine to reach a state ## Usage ``` fly machine wait [id] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for wait --state string Machine state to wait for (default "settled") -w, --wait-timeout duration Time duration to wait for the machine to reach the requested state. (default 5m0s) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly machine](/flyctl/cmd/fly_machine) - Manage Fly Machines. # fly mcp Source: https://docs.fly.io/flyctl/cmd/fly_mcp flyctl Model Context Protocol. ## Usage ``` fly mcp [command] [flags] ``` ## Available Commands * [add](/flyctl/cmd/fly_mcp_add) - \[experimental] Add MCP proxy client to a MCP client configuration * [destroy](/flyctl/cmd/fly_mcp_destroy) - \[experimental] Destroy an MCP stdio server * [inspect](/flyctl/cmd/fly_mcp_inspect) - \[experimental] Inspect a MCP stdio server * [launch](/flyctl/cmd/fly_mcp_launch) - \[experimental] Launch an MCP stdio server * [list](/flyctl/cmd/fly_mcp_list) - \[experimental] List MCP servers * [logs](/flyctl/cmd/fly_mcp_logs) - \[experimental] Show log for an MCP server * [proxy](/flyctl/cmd/fly_mcp_proxy) - \[experimental] Start an MCP proxy client * [remove](/flyctl/cmd/fly_mcp_remove) - \[experimental] Remove MCP proxy client from a MCP client configuration * [server](/flyctl/cmd/fly_mcp_server) - \[experimental] Start a flyctl MCP server * [wrap](/flyctl/cmd/fly_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](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly mcp add Source: https://docs.fly.io/flyctl/cmd/fly_mcp_add \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp destroy Source: https://docs.fly.io/flyctl/cmd/fly_mcp_destroy \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp inspect Source: https://docs.fly.io/flyctl/cmd/fly_mcp_inspect \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp launch Source: https://docs.fly.io/flyctl/cmd/fly_mcp_launch \[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') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp list Source: https://docs.fly.io/flyctl/cmd/fly_mcp_list \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp logs Source: https://docs.fly.io/flyctl/cmd/fly_mcp_logs \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp proxy Source: https://docs.fly.io/flyctl/cmd/fly_mcp_proxy \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp remove Source: https://docs.fly.io/flyctl/cmd/fly_mcp_remove \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp server Source: https://docs.fly.io/flyctl/cmd/fly_mcp_server \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mcp wrap Source: https://docs.fly.io/flyctl/cmd/fly_mcp_wrap \[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](/flyctl/cmd/fly_mcp) - flyctl Model Context Protocol. # fly mpg Source: https://docs.fly.io/flyctl/cmd/fly_mpg Manage Managed Postgres clusters. ## Usage ``` fly mpg [command] [flags] ``` ## Available Commands * [attach](/flyctl/cmd/fly_mpg_attach) - Attach a managed Postgres cluster to an app * [backup](/flyctl/cmd/fly_mpg_backup) - Backup commands * [connect](/flyctl/cmd/fly_mpg_connect) - Connect to a MPG database using psql * [create](/flyctl/cmd/fly_mpg_create) - Create a new Managed Postgres cluster * [databases](/flyctl/cmd/fly_mpg_databases) - Manage databases in a managed postgres cluster * [destroy](/flyctl/cmd/fly_mpg_destroy) - Destroy a managed Postgres cluster * [detach](/flyctl/cmd/fly_mpg_detach) - Detach a managed Postgres cluster from an app * [fork](/flyctl/cmd/fly_mpg_fork) - Fork MPG cluster into a new cluster from its current data. * [list](/flyctl/cmd/fly_mpg_list) - List MPG clusters. * [proxy](/flyctl/cmd/fly_mpg_proxy) - Proxy to a MPG database * [restore](/flyctl/cmd/fly_mpg_restore) - Restore MPG cluster from a backup or a point in time into a new cluster. * [status](/flyctl/cmd/fly_mpg_status) - Show MPG cluster status. * [users](/flyctl/cmd/fly_mpg_users) - Manage users in a managed postgres cluster ## 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](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly mpg attach Source: https://docs.fly.io/flyctl/cmd/fly_mpg_attach 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 -d, --database string The database to connect to -h, --help help for attach -u, --username string The username to connect as --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](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg backup Source: https://docs.fly.io/flyctl/cmd/fly_mpg_backup Backup commands ## Usage ``` fly mpg backup [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_mpg_backup_create) - Create MPG cluster backup. * [list](/flyctl/cmd/fly_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](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg backup create Source: https://docs.fly.io/flyctl/cmd/fly_mpg_backup_create 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](/flyctl/cmd/fly_mpg_backup) - Backup commands # fly mpg backup list Source: https://docs.fly.io/flyctl/cmd/fly_mpg_backup_list 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](/flyctl/cmd/fly_mpg_backup) - Backup commands # fly mpg connect Source: https://docs.fly.io/flyctl/cmd/fly_mpg_connect 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 -u, --username string The username to connect as ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg create Source: https://docs.fly.io/flyctl/cmd/fly_mpg_create 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 --pg-major-version int The major version of Postgres to use for the Postgres cluster. Supported versions are 16 and 17. (default 16) --plan string The plan to use for the Postgres cluster: Basic, Starter, Launch, Scale, Performance -r, --region string The target region (see 'flyctl platform regions') --volume-size int Initial 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](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg databases Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases Manage databases in a managed postgres cluster ## Usage ``` fly mpg databases [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_mpg_databases_create) - Create a database in an MPG cluster. * [extensions](/flyctl/cmd/fly_mpg_databases_extensions) - Manage Postgres extensions in a managed postgres cluster database * [list](/flyctl/cmd/fly_mpg_databases_list) - List databases in an MPG cluster. ## Options ``` -h, --help help for databases ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg databases create Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases_create Create a new database in a Managed Postgres cluster. ## Usage ``` fly mpg databases create [flags] ``` ## Options ``` -h, --help help for create -n, --name string The name of the database ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg databases](/flyctl/cmd/fly_mpg_databases) - Manage databases in a managed postgres cluster # fly mpg databases extensions Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases_extensions Manage Postgres extensions in a managed postgres cluster database ## Usage ``` fly mpg databases extensions [command] [flags] ``` ## Available Commands * [disable](/flyctl/cmd/fly_mpg_databases_extensions_disable) - Disable a Postgres extension on a database. * [enable](/flyctl/cmd/fly_mpg_databases_extensions_enable) - Enable a Postgres extension on a database. * [list](/flyctl/cmd/fly_mpg_databases_extensions_list) - List Postgres extensions for a database in an MPG cluster. ## Options ``` -h, --help help for extensions ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg databases](/flyctl/cmd/fly_mpg_databases) - Manage databases in a managed postgres cluster # fly mpg databases extensions disable Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases_extensions_disable Disable a Postgres extension on a database in a Managed Postgres cluster. ## Usage ``` fly mpg databases extensions disable [flags] ``` ## Options ``` -c, --cluster string Target cluster ID -d, --database string Target database within the cluster --force Drop dependent objects as well (CASCADE) -h, --help help for disable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg databases extensions](/flyctl/cmd/fly_mpg_databases_extensions) - Manage Postgres extensions in a managed postgres cluster database # fly mpg databases extensions enable Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases_extensions_enable Enable a Postgres extension on a database in a Managed Postgres cluster. ## Usage ``` fly mpg databases extensions enable [flags] ``` ## Options ``` -c, --cluster string Target cluster ID --create-schema Create the schema if it does not exist -d, --database string Target database within the cluster -h, --help help for enable --schema string Schema in which to create the extension (default: public) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg databases extensions](/flyctl/cmd/fly_mpg_databases_extensions) - Manage Postgres extensions in a managed postgres cluster database # fly mpg databases extensions list Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases_extensions_list List Postgres extensions in a database, showing which are currently installed. ## Usage ``` fly mpg databases extensions list [flags] ``` ## Options ``` -d, --database string Target database within the cluster -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 databases extensions](/flyctl/cmd/fly_mpg_databases_extensions) - Manage Postgres extensions in a managed postgres cluster database # fly mpg databases list Source: https://docs.fly.io/flyctl/cmd/fly_mpg_databases_list List databases in a Managed Postgres cluster. ## Usage ``` fly mpg databases list [flags] ``` ## Options ``` -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 databases](/flyctl/cmd/fly_mpg_databases) - Manage databases in a managed postgres cluster # fly mpg destroy Source: https://docs.fly.io/flyctl/cmd/fly_mpg_destroy 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg detach Source: https://docs.fly.io/flyctl/cmd/fly_mpg_detach Detach a managed Postgres cluster from an app. This command will remove the attachment record linking the app to the cluster. Note: This does NOT remove any secrets from the app. Use 'fly secrets unset' to remove secrets. ## Usage ``` fly mpg detach [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for detach ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg fork Source: https://docs.fly.io/flyctl/cmd/fly_mpg_fork Fork a Managed Postgres cluster into a new cluster from the source's current data, leaving the source cluster unchanged. The fork is provisioned asynchronously in the same organization and billed separately. The fork uses the source cluster's plan unless --plan picks a different one, including a smaller one. ## Usage ``` fly mpg fork [CLUSTER_ID] [flags] ``` ## Options ``` -h, --help help for fork -n, --name string The name of the forked cluster (defaults to -fork) --plan string The plan for the forked cluster: Basic, Starter, Launch, Scale, Performance (defaults to the source cluster's plan) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg list Source: https://docs.fly.io/flyctl/cmd/fly_mpg_list List MPG clusters owned by the specified organization. If no organization is specified, the user's personal organization is used. ## Usage ``` 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 ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg proxy Source: https://docs.fly.io/flyctl/cmd/fly_mpg_proxy 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 -p, --local-port string Local port to proxy on (default "16380") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg restore Source: https://docs.fly.io/flyctl/cmd/fly_mpg_restore Restore a Managed Postgres cluster from a backup or a point in time into a new cluster, leaving the source cluster unchanged. The restored cluster is provisioned asynchronously in the same organization and billed separately. Find backup IDs with 'fly mpg backup list'. ## Usage ``` fly mpg restore [flags] ``` ## Options ``` --backup-id string The backup ID to restore from -h, --help help for restore -n, --name string The name of the restored cluster (defaults to a generated name) --pitr-time string Restore to a specific point in time (RFC3339, e.g. 2026-06-01T12:00:00Z). Requires the cluster's PITR recovery window to cover this time. Mutually exclusive with --backup-id. ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg status Source: https://docs.fly.io/flyctl/cmd/fly_mpg_status 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](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg users Source: https://docs.fly.io/flyctl/cmd/fly_mpg_users Manage users in a managed postgres cluster ## Usage ``` fly mpg users [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_mpg_users_create) - Create a user in an MPG cluster. * [delete](/flyctl/cmd/fly_mpg_users_delete) - Delete a user from an MPG cluster. * [list](/flyctl/cmd/fly_mpg_users_list) - List users in an MPG cluster. * [rotate-password](/flyctl/cmd/fly_mpg_users_rotate-password) - Rotate a user's password in an MPG cluster. * [set-role](/flyctl/cmd/fly_mpg_users_set-role) - Update a user's role in an MPG cluster. ## Options ``` -h, --help help for users ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg](/flyctl/cmd/fly_mpg) - Manage Managed Postgres clusters. # fly mpg users create Source: https://docs.fly.io/flyctl/cmd/fly_mpg_users_create Create a new user in a Managed Postgres cluster. ## Usage ``` fly mpg users create [flags] ``` ## Options ``` -h, --help help for create -r, --role string The role of the user (schema_admin, writer, or reader) -u, --username string The username of the user ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg users](/flyctl/cmd/fly_mpg_users) - Manage users in a managed postgres cluster # fly mpg users delete Source: https://docs.fly.io/flyctl/cmd/fly_mpg_users_delete Delete a user from a Managed Postgres cluster. ## Usage ``` fly mpg users delete [flags] ``` ## Options ``` -h, --help help for delete -u, --username string The username to delete -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg users](/flyctl/cmd/fly_mpg_users) - Manage users in a managed postgres cluster # fly mpg users list Source: https://docs.fly.io/flyctl/cmd/fly_mpg_users_list List users in a Managed Postgres cluster. ## Usage ``` fly mpg users list [flags] ``` ## Options ``` -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 users](/flyctl/cmd/fly_mpg_users) - Manage users in a managed postgres cluster # fly mpg users rotate-password Source: https://docs.fly.io/flyctl/cmd/fly_mpg_users_rotate-password Rotate a user's password in a Managed Postgres cluster. Prints the new password. Update applications and DATABASE\_URL secrets that use the old password. ## Usage ``` fly mpg users rotate-password [flags] ``` ## Options ``` -h, --help help for rotate-password -j, --json JSON output --kill-sessions Terminate the user's existing database sessions after rotating the password -u, --username string The username whose password to rotate -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg users](/flyctl/cmd/fly_mpg_users) - Manage users in a managed postgres cluster # fly mpg users set-role Source: https://docs.fly.io/flyctl/cmd/fly_mpg_users_set-role Update a user's role in a Managed Postgres cluster. ## Usage ``` fly mpg users set-role [flags] ``` ## Options ``` -h, --help help for set-role -r, --role string The new role for the user (schema_admin, writer, or reader) -u, --username string The username to update ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly mpg users](/flyctl/cmd/fly_mpg_users) - Manage users in a managed postgres cluster # fly orgs Source: https://docs.fly.io/flyctl/cmd/fly_orgs Commands for managing Fly organizations. list, create, show and destroy organizations. Organization admins can also invite or remove users from Organizations. ## Usage ``` fly orgs [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_orgs_create) - Create an organization * [cross-network-replays](/flyctl/cmd/fly_orgs_cross-network-replays) - Manage cross-network replay settings * [delete](/flyctl/cmd/fly_orgs_delete) - Delete an organization * [invite](/flyctl/cmd/fly_orgs_invite) - Invite user (by email) to organization * [list](/flyctl/cmd/fly_orgs_list) - Lists organizations for current user * [remove](/flyctl/cmd/fly_orgs_remove) - Remove a user from an organization * [replay-sources](/flyctl/cmd/fly_orgs_replay-sources) - Manage allowed replay source organizations * [show](/flyctl/cmd/fly_orgs_show) - Show information about an organization ## Options ``` -h, --help help for orgs ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly orgs create Source: https://docs.fly.io/flyctl/cmd/fly_orgs_create Create a new organization. Other users can be invited to join the organization later. ## Usage ``` fly orgs create [name] [flags] ``` ## Options ``` -h, --help help for create -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 orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs cross-network-replays Source: https://docs.fly.io/flyctl/cmd/fly_orgs_cross-network-replays Commands for managing cross-network replay permissions. ## Usage ``` fly orgs cross-network-replays [command] [flags] ``` ## Available Commands * [disable](/flyctl/cmd/fly_orgs_cross-network-replays_disable) - Disable cross-network replays * [enable](/flyctl/cmd/fly_orgs_cross-network-replays_enable) - Enable cross-network replays * [status](/flyctl/cmd/fly_orgs_cross-network-replays_status) - Show cross-network replay status ## Options ``` -h, --help help for cross-network-replays ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs cross-network-replays disable Source: https://docs.fly.io/flyctl/cmd/fly_orgs_cross-network-replays_disable Disable cross-network replays for this organization. Replays will be scoped to apps within the same network. ## Usage ``` fly orgs cross-network-replays disable [flags] ``` ## Options ``` -h, --help help for disable -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs cross-network-replays](/flyctl/cmd/fly_orgs_cross-network-replays) - Manage cross-network replay settings # fly orgs cross-network-replays enable Source: https://docs.fly.io/flyctl/cmd/fly_orgs_cross-network-replays_enable Enable cross-network replays for this organization. Apps will be able to replay requests to apps in different networks within the same org. ## Usage ``` fly orgs cross-network-replays enable [flags] ``` ## Options ``` -h, --help help for enable -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs cross-network-replays](/flyctl/cmd/fly_orgs_cross-network-replays) - Manage cross-network replay settings # fly orgs cross-network-replays status Source: https://docs.fly.io/flyctl/cmd/fly_orgs_cross-network-replays_status Show whether cross-network replays are allowed for this organization. ## Usage ``` fly orgs cross-network-replays status [flags] ``` ## Options ``` -h, --help help for status -j, --json JSON output -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 orgs cross-network-replays](/flyctl/cmd/fly_orgs_cross-network-replays) - Manage cross-network replay settings # fly orgs delete Source: https://docs.fly.io/flyctl/cmd/fly_orgs_delete Delete an existing organization. ## Usage ``` fly orgs delete [-yes] [slug] [flags] ``` ## Options ``` -h, --help help for delete -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs invite Source: https://docs.fly.io/flyctl/cmd/fly_orgs_invite Invite a user, by email, to join organization. The invitation will be sent, and the user will be pending until they respond. ## Usage ``` fly orgs invite [slug] [email] [flags] ``` ## Options ``` -h, --help help for invite -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 orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs list Source: https://docs.fly.io/flyctl/cmd/fly_orgs_list Lists organizations available to current user. ## Usage ``` fly orgs list [flags] ``` ## Options ``` -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 orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs remove Source: https://docs.fly.io/flyctl/cmd/fly_orgs_remove Remove a user from an organization. User must have accepted a previous invitation to join (if not, see orgs revoke). ## Usage ``` fly orgs remove [slug] [email] [flags] ``` ## Options ``` -h, --help help for remove ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs replay-sources Source: https://docs.fly.io/flyctl/cmd/fly_orgs_replay-sources Commands for managing cross-organization replay permissions. ## Usage ``` fly orgs replay-sources [command] [flags] ``` ## Available Commands * [add](/flyctl/cmd/fly_orgs_replay-sources_add) - Add allowed replay source organizations * [list](/flyctl/cmd/fly_orgs_replay-sources_list) - List allowed replay source organizations * [remove](/flyctl/cmd/fly_orgs_replay-sources_remove) - Remove allowed replay source organizations ## Options ``` -h, --help help for replay-sources ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly orgs replay-sources add Source: https://docs.fly.io/flyctl/cmd/fly_orgs_replay-sources_add Add organizations to the list of allowed replay sources for this organization. If no slugs are provided, an interactive selector will be shown. ## Usage ``` fly orgs replay-sources add [...] [flags] ``` ## Options ``` -h, --help help for add -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 orgs replay-sources](/flyctl/cmd/fly_orgs_replay-sources) - Manage allowed replay source organizations # fly orgs replay-sources list Source: https://docs.fly.io/flyctl/cmd/fly_orgs_replay-sources_list List organizations allowed to replay requests to this organization. ## Usage ``` fly orgs replay-sources list [flags] ``` ## Options ``` -h, --help help for list -j, --json JSON output -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 orgs replay-sources](/flyctl/cmd/fly_orgs_replay-sources) - Manage allowed replay source organizations # fly orgs replay-sources remove Source: https://docs.fly.io/flyctl/cmd/fly_orgs_replay-sources_remove Remove organizations from the list of allowed replay sources for this organization. If no slugs are provided, an interactive selector will be shown. ## Usage ``` fly orgs replay-sources remove [...] [flags] ``` ## Options ``` -h, --help help for remove -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly orgs replay-sources](/flyctl/cmd/fly_orgs_replay-sources) - Manage allowed replay source organizations # fly orgs show Source: https://docs.fly.io/flyctl/cmd/fly_orgs_show Shows information about an organization. Includes name, slug and type. Summarizes user permissions, DNS zones and associated member. Details full list of members and roles. ## Usage ``` fly orgs show [slug] [flags] ``` ## Options ``` -h, --help help for show -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 orgs](/flyctl/cmd/fly_orgs) - Commands for managing Fly organizations # fly ping Source: https://docs.fly.io/flyctl/cmd/fly_ping Test connectivity with ICMP ping messages. This runs over WireGuard; tell us which WireGuard tunnel to use by running from within an app directory (with a 'fly.toml'), passing the '-a' flag with an app name, or the '-o' flag with an org name. With no arguments, test connectivity to your gateway, the first hop in our network, to see if your WireGuard connection is working. The target argument can be either a ".internal" DNS name in our network (the name of your application) or "gateway". ## Usage ``` fly ping [hostname] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -n, --count int Number of probes to send (0=indefinite) -h, --help help for ping -i, --interval string Interval between ping probes (default "1s") -o, --org string The target Fly.io organization -s, --size int Size of probe to send (not including headers) (default 12) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly platform Source: https://docs.fly.io/flyctl/cmd/fly_platform The PLATFORM commands are for users looking for information about the Fly platform. ## Usage ``` fly platform [command] [flags] ``` ## Available Commands * [regions](/flyctl/cmd/fly_platform_regions) - List regions * [status](/flyctl/cmd/fly_platform_status) - Show current platform status with an optional filter for maintenance or incidents in json mode (eg. incidents, maintenance) * [vm-sizes](/flyctl/cmd/fly_platform_vm-sizes) - List VM Sizes ## Options ``` -h, --help help for platform ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly platform regions Source: https://docs.fly.io/flyctl/cmd/fly_platform_regions View a list of regions where Fly has datacenters. ## Usage ``` fly platform regions [flags] ``` ## Options ``` -h, --help help for regions -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 platform](/flyctl/cmd/fly_platform) - Fly platform information # fly platform status Source: https://docs.fly.io/flyctl/cmd/fly_platform_status Show current Fly platform status in a browser or via json with the json flag ## Usage ``` fly platform status [kind] [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 platform](/flyctl/cmd/fly_platform) - Fly platform information # fly platform vm-sizes Source: https://docs.fly.io/flyctl/cmd/fly_platform_vm-sizes View a list of VM sizes which can be used with the FLYCTL SCALE VM command ## Usage ``` fly platform vm-sizes [flags] ``` ## Options ``` -h, --help help for vm-sizes -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 platform](/flyctl/cmd/fly_platform) - Fly platform information # fly postgres Source: https://docs.fly.io/flyctl/cmd/fly_postgres 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/](https://fly.io/docs/mpg/overview/) for more information about Managed Postgres. ## Usage ``` fly postgres [command] [flags] ``` ## Available Commands * [attach](/flyctl/cmd/fly_postgres_attach) - Attach a postgres cluster to an app * [backup](/flyctl/cmd/fly_postgres_backup) - Backup commands * [config](/flyctl/cmd/fly_postgres_config) - Show and manage Postgres configuration. * [connect](/flyctl/cmd/fly_postgres_connect) - Connect to the Postgres console * [create](/flyctl/cmd/fly_postgres_create) - Create a new Postgres cluster * [db](/flyctl/cmd/fly_postgres_db) - Manage databases in a cluster * [detach](/flyctl/cmd/fly_postgres_detach) - Detach a postgres cluster from an app * [events](/flyctl/cmd/fly_postgres_events) - Track major cluster events * [failover](/flyctl/cmd/fly_postgres_failover) - Failover to a new primary * [import](/flyctl/cmd/fly_postgres_import) - Imports database from a specified Postgres URI * [list](/flyctl/cmd/fly_postgres_list) - List postgres clusters * [renew-certs](/flyctl/cmd/fly_postgres_renew-certs) - Renews the SSH certificates for the Postgres cluster. * [restart](/flyctl/cmd/fly_postgres_restart) - Restarts each member of the Postgres cluster one by one. * [users](/flyctl/cmd/fly_postgres_users) - Manage users in a postgres cluster ## Options ``` -h, --help help for postgres ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly postgres attach Source: https://docs.fly.io/flyctl/cmd/fly_postgres_attach Attach a postgres cluster to an app ## Usage ``` fly postgres attach [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --database-name string The designated database name for this consuming app. --database-user string The database user to create. By default, we will use the name of the consuming app. -h, --help help for attach --superuser Grants attached user superuser privileges (default true) --variable-name string The environment variable name that will be added to the consuming app. (default "DATABASE_URL") -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres backup Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup Backup commands ## Usage ``` fly postgres backup [command] [flags] ``` ## Available Commands * [config](/flyctl/cmd/fly_postgres_backup_config) - Manage backup configuration * [create](/flyctl/cmd/fly_postgres_backup_create) - Create a backup * [enable](/flyctl/cmd/fly_postgres_backup_enable) - Enable backups on a Postgres cluster, creating a Tigris bucket for storage * [list](/flyctl/cmd/fly_postgres_backup_list) - List backups * [restore](/flyctl/cmd/fly_postgres_backup_restore) - Performs a WAL-based restore into a new Postgres cluster. ## 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 postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres backup config Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_config Manage backup configuration ## Usage ``` fly postgres backup config [command] [flags] ``` ## Available Commands * [show](/flyctl/cmd/fly_postgres_backup_config_show) - Show backup configuration * [update](/flyctl/cmd/fly_postgres_backup_config_update) - Update backup configuration ## Options ``` -h, --help help for config ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup](/flyctl/cmd/fly_postgres_backup) - Backup commands # fly postgres backup config show Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_config_show Show backup configuration ## Usage ``` fly postgres backup config show [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for show ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup config](/flyctl/cmd/fly_postgres_backup_config) - Manage backup configuration # fly postgres backup config update Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_config_update Update backup configuration ## Usage ``` fly postgres backup config update [flags] ``` ## Options ``` -a, --app string Application name --archive-timeout string Archive timeout -c, --config string Path to application configuration file --full-backup-frequency string Full backup frequency -h, --help help for update --minimum-redundancy string Minimum redundancy --recovery-window string Recovery window ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup config](/flyctl/cmd/fly_postgres_backup_config) - Manage backup configuration # fly postgres backup create Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_create Create a backup ## Usage ``` fly postgres backup create [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for create -i, --immediate-checkpoint Forces Postgres to perform an immediate checkpoint -n, --name string Backup name ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup](/flyctl/cmd/fly_postgres_backup) - Backup commands # fly postgres backup enable Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_enable Enable backups on a Postgres cluster, creating a Tigris bucket for storage ## Usage ``` fly postgres backup enable [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for enable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup](/flyctl/cmd/fly_postgres_backup) - Backup commands # fly postgres backup list Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_list List backups ## Usage ``` fly postgres backup list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for list ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup](/flyctl/cmd/fly_postgres_backup) - Backup commands # fly postgres backup restore Source: https://docs.fly.io/flyctl/cmd/fly_postgres_backup_restore Performs a WAL-based restore into a new Postgres cluster. ## Usage ``` fly postgres backup restore [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --detach Return immediately instead of monitoring deployment progress -h, --help help for restore --image-ref string Specify a non-default base image for the restored Postgres app --restore-target-inclusive Set to true to stop recovery after the specified time, or false to stop before it (default true) --restore-target-name string ID or alias of backup to restore. --restore-target-time string RFC3339-formatted timestamp up to which recovery will proceed. Example: 2021-07-16T12:34:56Z ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres backup](/flyctl/cmd/fly_postgres_backup) - Backup commands # fly postgres config Source: https://docs.fly.io/flyctl/cmd/fly_postgres_config Show and manage Postgres configuration. ## Usage ``` fly postgres config [command] [flags] ``` ## Available Commands * [show](/flyctl/cmd/fly_postgres_config_show) - Show Postgres configuration * [update](/flyctl/cmd/fly_postgres_config_update) - Update Postgres configuration. ## Options ``` -h, --help help for config ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres config show Source: https://docs.fly.io/flyctl/cmd/fly_postgres_config_show Show Postgres configuration ## Usage ``` fly postgres config show [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for show ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres config](/flyctl/cmd/fly_postgres_config) - Show and manage Postgres configuration. # fly postgres config update Source: https://docs.fly.io/flyctl/cmd/fly_postgres_config_update Update Postgres configuration. ## Usage ``` fly postgres config update [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --detach Return immediately instead of monitoring deployment progress --force Skips pg-setting value verification. -h, --help help for update --log-min-duration-statement string Sets the minimum execution time above which all statements will be logged. (ms) --log-statement string Sets the type of statements logged. (none, ddl, mod, all) --maintenance-work-mem string Sets the maximum amount of memory used for maintenance operations like ALTER TABLE, CREATE INDEX, and VACUUM --max-connections string Sets the maximum number of concurrent connections. --max-replication-slots string Specifies the maximum number of replication slots. This should typically match max_wal_senders. --max-wal-senders string Maximum number of concurrent connections from standby servers or streaming backup clients. (0 disables replication) --shared-buffers string Sets the amount of memory the database server uses for shared memory buffers --shared-preload-libraries string Sets the shared libraries to preload. (comma separated string) --wal-level string Sets the level of information written to the WAL. (minimal, replica, logical). --work-mem string Sets the maximum amount of memory each Postgres query can use -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres config](/flyctl/cmd/fly_postgres_config) - Show and manage Postgres configuration. # fly postgres connect Source: https://docs.fly.io/flyctl/cmd/fly_postgres_connect Connect to the Postgres console ## Usage ``` fly postgres connect [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -d, --database string The name of the database you would like to connect to (default "postgres") -h, --help help for connect -p, --password string The postgres user password -u, --user string The postgres user to connect with (default "postgres") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres create Source: https://docs.fly.io/flyctl/cmd/fly_postgres_create Create a new Postgres cluster ## Usage ``` fly postgres create [flags] ``` ## Options ``` --autostart Automatically start a stopped Postgres app when a network request is received --consul-url string Opt into using an existing consul as the backend store by specifying the target consul url. --detach Return immediately instead of monitoring deployment progress --enable-backups Create a new tigris bucket and enable WAL-based backups --flex Create a postgres cluster that's managed by Repmgr (default true) --fork-from string Specify a source Postgres application to fork from. Format: or : --generate-name Generate an app name -h, --help help for create --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --image-ref string Specify a non-default base image for the Postgres app --initial-cluster-size int Initial cluster size -n, --name string The name of your Postgres app -o, --org string The target Fly.io organization -p, --password string The superuser password. The password will be generated for you if you leave this blank -r, --region string The target region (see 'flyctl platform regions') --snapshot-id string Creates the volume with the contents of the snapshot --stolon Create a postgres cluster that's managed by Stolon --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values --volume-size int The volume size in GB ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres db Source: https://docs.fly.io/flyctl/cmd/fly_postgres_db Manage databases in a cluster ## Usage ``` fly postgres db [command] [flags] ``` ## Available Commands * [list](/flyctl/cmd/fly_postgres_db_list) - list databases ## Options ``` -h, --help help for db -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 postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres db list Source: https://docs.fly.io/flyctl/cmd/fly_postgres_db_list list databases ## Usage ``` fly postgres db list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for list ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres db](/flyctl/cmd/fly_postgres_db) - Manage databases in a cluster # fly postgres detach Source: https://docs.fly.io/flyctl/cmd/fly_postgres_detach Detach a postgres cluster from an app ## Usage ``` fly postgres detach [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for detach ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres events Source: https://docs.fly.io/flyctl/cmd/fly_postgres_events Track major cluster events ## Usage ``` fly postgres events [command] [flags] ``` ## Available Commands * [list](/flyctl/cmd/fly_postgres_events_list) - Outputs a formatted list of cluster events ## Options ``` -h, --help help for events -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 postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres events list Source: https://docs.fly.io/flyctl/cmd/fly_postgres_events_list Outputs a formatted list of cluster events ## Usage ``` fly postgres events list [flags] ``` ## Options ``` -o, --all Outputs all entries -a, --app string Application name -d, --compact Omit the 'Details' column -c, --config string Path to application configuration file -e, --event string Event type in a postgres cluster -h, --help help for list -l, --limit string Set the maximum number of entries to output (default: 20) -i, --node-id string Restrict entries to node with this ID -n, --node-name string Restrict entries to node with this name ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres events](/flyctl/cmd/fly_postgres_events) - Track major cluster events # fly postgres failover Source: https://docs.fly.io/flyctl/cmd/fly_postgres_failover Failover to a new primary ## Usage ``` fly postgres failover [flags] ``` ## Options ``` --allow-secondary-region Allow failover to a machine in a secondary region. This is useful when the primary region is unavailable, but the secondary region is still healthy. This is only available for flex machines. -a, --app string Application name -c, --config string Path to application configuration file --force Force a failover even if we can't connect to the active leader -h, --help help for failover ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres import Source: https://docs.fly.io/flyctl/cmd/fly_postgres_import Imports database from a specified Postgres URI ## Usage ``` fly postgres import [flags] ``` ## Options ``` -a, --app string Application name --clean Drop database objects prior to creating them. -c, --config string Path to application configuration file --create Begin by creating the database itself and reconnecting to it. If --clean is also specified, the script drops and recreates the target database before reconnecting to it. (default true) --data-only Dump only the data, not the schema (data definitions). -h, --help help for import --image string Path to public image containing custom migration process --no-owner Do not set ownership of objects to match the original database. Makes dump restorable by any user. (default true) --region string Region to provision migration machine --vm-size string the size of the VM ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres list Source: https://docs.fly.io/flyctl/cmd/fly_postgres_list List postgres clusters ## Usage ``` fly postgres list [flags] ``` ## Options ``` -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 postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres renew-certs Source: https://docs.fly.io/flyctl/cmd/fly_postgres_renew-certs Renews the SSH certificates for the Postgres cluster. This is useful when the certificates have expired or need to be rotated. ## Usage ``` fly postgres renew-certs [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for renew-certs --valid-days int The number of days the certificate should be valid for. (default 36525) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres restart Source: https://docs.fly.io/flyctl/cmd/fly_postgres_restart Restarts each member of the Postgres cluster one by one. Downtime should be minimal. ## Usage ``` fly postgres restart [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file --force Force a restart even we don't have an active leader -h, --help help for restart --skip-health-checks Runs rolling restart process without waiting for health checks. ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres users Source: https://docs.fly.io/flyctl/cmd/fly_postgres_users Manage users in a postgres cluster ## Usage ``` fly postgres users [command] [flags] ``` ## Available Commands * [list](/flyctl/cmd/fly_postgres_users_list) - List users ## Options ``` -h, --help help for users ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly postgres](/flyctl/cmd/fly_postgres) - Unmanaged Postgres cluster commands # fly postgres users list Source: https://docs.fly.io/flyctl/cmd/fly_postgres_users_list List users ## Usage ``` fly postgres users list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -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 postgres users](/flyctl/cmd/fly_postgres_users) - Manage users in a postgres cluster # fly proxy Source: https://docs.fly.io/flyctl/cmd/fly_proxy Proxies connections to a Fly Machine through a WireGuard tunnel. By default, connects to the first Machine address returned by an internal DNS query on the app. ## Usage ``` fly proxy [remote_host] [flags] ``` ## Options ``` -a, --app string Application name -b, --bind-addr string Local address to bind to (default "127.0.0.1") -c, --config string Path to application configuration file -h, --help help for proxy -o, --org string The target Fly.io organization -q, --quiet Don't print progress indicators for WireGuard -s, --select Prompt to select from available Machines from the current application --watch-stdin Watches stdin and terminates once it gets closed ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly redis Source: https://docs.fly.io/flyctl/cmd/fly_redis Launch and manage Redis databases managed by Upstash.com ## Usage ``` fly redis [command] [flags] ``` ## Available Commands * [connect](/flyctl/cmd/fly_redis_connect) - Connect to a Redis database using redis-cli * [create](/flyctl/cmd/fly_redis_create) - Create an Upstash Redis database * [dashboard](/flyctl/cmd/fly_redis_dashboard) - View your Upstash Redis databases on the Upstash web console * [destroy](/flyctl/cmd/fly_redis_destroy) - Permanently destroy an Upstash Redis database * [list](/flyctl/cmd/fly_redis_list) - List Upstash Redis databases for an organization * [plans](/flyctl/cmd/fly_redis_plans) - List available Redis plans * [proxy](/flyctl/cmd/fly_redis_proxy) - Proxy to a Redis database * [reset](/flyctl/cmd/fly_redis_reset) - Reset the password for an Upstash Redis database * [status](/flyctl/cmd/fly_redis_status) - Show status of a Redis database * [update](/flyctl/cmd/fly_redis_update) - Update an Upstash Redis database ## Options ``` -h, --help help for redis ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly redis connect Source: https://docs.fly.io/flyctl/cmd/fly_redis_connect Connect to a Redis database using redis-cli ## Usage ``` fly redis connect [flags] ``` ## Options ``` -h, --help help for connect -o, --org string The target Fly.io organization -r, --region string The target region (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis create Source: https://docs.fly.io/flyctl/cmd/fly_redis_create Create an Upstash Redis database ## Usage ``` fly redis create [flags] ``` ## Options ``` --disable-eviction Disallow writes when the max data size limit has been reached --enable-auto-upgrade Automatically upgrade to a higher plan when hitting resource limits --enable-eviction Evict objects when memory is full --enable-prodpack Enable ProdPack add-on for additional features ($200/mo) -h, --help help for create -n, --name string The name of your Redis database --no-replicas Don't prompt for selecting replica regions -o, --org string The target Fly.io organization --plan string The plan for your Redis database (default: pay-as-you-go) -r, --region string The target region (see 'flyctl platform regions') --replica-regions string Comma-separated list of regions to deploy read replicas (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis dashboard Source: https://docs.fly.io/flyctl/cmd/fly_redis_dashboard View your Upstash Redis databases on the Upstash web console ## Usage ``` fly redis dashboard [flags] ``` ## Options ``` -h, --help help for dashboard ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis destroy Source: https://docs.fly.io/flyctl/cmd/fly_redis_destroy Permanently destroy an Upstash Redis database ## Usage ``` fly redis destroy [flags] ``` ## Options ``` -h, --help help for destroy -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis list Source: https://docs.fly.io/flyctl/cmd/fly_redis_list List Upstash Redis databases for an organization ## Usage ``` fly redis list [flags] ``` ## Options ``` -h, --help help for list -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 redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis plans Source: https://docs.fly.io/flyctl/cmd/fly_redis_plans List available Redis plans ## Usage ``` fly redis plans [flags] ``` ## Options ``` -h, --help help for plans ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis proxy Source: https://docs.fly.io/flyctl/cmd/fly_redis_proxy Proxy to a Redis database ## Usage ``` fly redis proxy [flags] ``` ## Options ``` -h, --help help for proxy -o, --org string The target Fly.io organization -r, --region string The target region (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis reset Source: https://docs.fly.io/flyctl/cmd/fly_redis_reset Reset the password for an Upstash Redis database ## Usage ``` fly redis reset [flags] ``` ## Options ``` -h, --help help for reset ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis status Source: https://docs.fly.io/flyctl/cmd/fly_redis_status Show status of a Redis database ## Usage ``` fly redis status [flags] ``` ## Options ``` -h, --help help for status ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly redis update Source: https://docs.fly.io/flyctl/cmd/fly_redis_update Update an Upstash Redis database ## Usage ``` fly redis update [flags] ``` ## Options ``` --disable-prodpack Disable the ProdPack add-on, leaving every other setting untouched --enable-prodpack Enable ProdPack add-on for additional features ($200/mo), leaving every other setting untouched -h, --help help for update -o, --org string The target Fly.io organization -r, --region string The target region (see 'flyctl platform regions') --replica-regions string Comma-separated list of regions to deploy read replicas (see 'flyctl platform regions') ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly redis](/flyctl/cmd/fly_redis) - Launch and manage Redis databases managed by Upstash.com # fly releases Source: https://docs.fly.io/flyctl/cmd/fly_releases List all the releases of the application onto the Fly platform, including type, when, success/fail and which user triggered the release. ## Usage ``` fly releases [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for releases --image Display the Docker image reference of the release -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](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly scale Source: https://docs.fly.io/flyctl/cmd/fly_scale Scale application resources ## Usage ``` fly scale [command] [flags] ``` ## Available Commands * [count](/flyctl/cmd/fly_scale_count) - Change an app's VM count to the given value * [memory](/flyctl/cmd/fly_scale_memory) - Set VM memory * [show](/flyctl/cmd/fly_scale_show) - Show current resources * [vm](/flyctl/cmd/fly_scale_vm) - Change an app's VM to a named size (eg. shared-cpu-1x, performance-1x, performance-2x...) ## Options ``` -h, --help help for scale ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly scale count Source: https://docs.fly.io/flyctl/cmd/fly_scale_count Change an app's VM count to the given value. For pricing, see [https://fly.io/docs/about/pricing/](https://fly.io/docs/about/pricing/) ## Usage ``` fly scale count [count] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -e, --env stringArray Set of environment variables in the form of NAME=VALUE pairs. Can be specified multiple times. --from-snapshot string New volumes are restored from snapshot, use 'last' for most recent snapshot. The default is an empty volume -h, --help help for count --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) --max-per-region int Max number of VMs per region (default -1) -g, --process-group string The process group to scale -r, --region string Comma separated list of regions to act on. Defaults to all regions where there is at least one machine running for the app --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values --with-new-volumes New machines each get a new volumes even if there are unattached volumes available -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly scale](/flyctl/cmd/fly_scale) - Scale app resources # fly scale memory Source: https://docs.fly.io/flyctl/cmd/fly_scale_memory Set VM memory to a number of megabytes ## Usage ``` fly scale memory [memoryMB] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for memory -g, --process-group string The process group to apply the VM size to ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly scale](/flyctl/cmd/fly_scale) - Scale app resources # fly scale show Source: https://docs.fly.io/flyctl/cmd/fly_scale_show Show current VM size and counts ## Usage ``` fly scale show [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for show -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 scale](/flyctl/cmd/fly_scale) - Scale app resources # fly scale vm Source: https://docs.fly.io/flyctl/cmd/fly_scale_vm 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`. 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/](https://fly.io/docs/about/pricing/) ## Usage ``` fly scale vm [size] [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for vm -g, --process-group string The process group to apply the VM size to --vm-memory int Memory in MB for the VM ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly scale](/flyctl/cmd/fly_scale) - Scale app resources # fly secrets Source: https://docs.fly.io/flyctl/cmd/fly_secrets Secrets are provided to applications at runtime as ENV variables. Names are case sensitive and stored as-is, so ensure names are appropriate for the application and vm environment. ## Usage ``` fly secrets [command] [flags] ``` ## Available Commands * [deploy](/flyctl/cmd/fly_secrets_deploy) - Deploy staged secrets for an application * [import](/flyctl/cmd/fly_secrets_import) - Set secrets as NAME=VALUE pairs from stdin * [list](/flyctl/cmd/fly_secrets_list) - List application secret names, digests and deployment status * [set](/flyctl/cmd/fly_secrets_set) - Set one or more encrypted secrets for an application * [sync](/flyctl/cmd/fly_secrets_sync) - Sync flyctl with the latest versions of app secrets, even if they were set elsewhere * [unset](/flyctl/cmd/fly_secrets_unset) - Unset one or more encrypted secrets for an application ## Options ``` -h, --help help for secrets ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly secrets deploy Source: https://docs.fly.io/flyctl/cmd/fly_secrets_deploy Deploy staged secrets for an application ## Usage ``` fly secrets deploy [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 deploy ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly secrets](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. # fly secrets import Source: https://docs.fly.io/flyctl/cmd/fly_secrets_import Set one or more encrypted secrets for an application. Values are read from stdin as NAME=VALUE pairs ## Usage ``` fly secrets import [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 import --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](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. # fly secrets list Source: https://docs.fly.io/flyctl/cmd/fly_secrets_list List the secrets available to the application. It shows each secret's name, a digest of its value and the deployment status across machines. The actual value of the secret is only available to the application. Secrets that need deployment are prefixed with an indicator: * Staged secret (not deployed to any machines) ! Partial deployment (deployed to some but not all machines) Deployment status: Deployed - Secret is deployed to all machines (secret updated\_at \<= machine release created\_at) Staged - Secret is staged but not deployed to any machines Partial - Secret is deployed to some but not all machines (rolling deployment in progress) Unknown - Status cannot be determined (missing timestamps, too many machines, or API error) ## Usage ``` fly secrets list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -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 secrets](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. # fly secrets set Source: https://docs.fly.io/flyctl/cmd/fly_secrets_set Set one or more encrypted secrets for an application ## Usage ``` fly secrets set [flags] NAME=VALUE NAME=VALUE ... ``` ## 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 set --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](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. # fly secrets sync Source: https://docs.fly.io/flyctl/cmd/fly_secrets_sync 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](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. # fly secrets unset Source: https://docs.fly.io/flyctl/cmd/fly_secrets_unset Unset one or more encrypted secrets for an application ## Usage ``` fly secrets unset [flags] NAME NAME ... ``` ## 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 unset --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](/flyctl/cmd/fly_secrets) - Manage application secrets with the set and unset commands. # fly services Source: https://docs.fly.io/flyctl/cmd/fly_services Shows information about the services of the application. ## Usage ``` fly services [command] [flags] ``` ## Available Commands * [list](/flyctl/cmd/fly_services_list) - List services ## Options ``` -h, --help help for services ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly services list Source: https://docs.fly.io/flyctl/cmd/fly_services_list List the services that are associated with an app ## Usage ``` fly services list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for list ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly services](/flyctl/cmd/fly_services) - Show the application's services # fly settings Source: https://docs.fly.io/flyctl/cmd/fly_settings Manage flyctl settings ## Usage ``` fly settings [command] [flags] ``` ## Available Commands * [analytics](/flyctl/cmd/fly_settings_analytics) - Control anonymized analytics collection * [autoupdate](/flyctl/cmd/fly_settings_autoupdate) - Control automatic updates * [synthetics](/flyctl/cmd/fly_settings_synthetics) - Control synthetics agent execution ## Options ``` -h, --help help for settings ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly settings analytics Source: https://docs.fly.io/flyctl/cmd/fly_settings_analytics Control anonymized analytics collection ## Usage ``` fly settings analytics [flags] ``` ## Available Commands * [disable](/flyctl/cmd/fly_settings_analytics_disable) - Disable analytics * [enable](/flyctl/cmd/fly_settings_analytics_enable) - Enable analytics ## Options ``` -h, --help help for analytics ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings](/flyctl/cmd/fly_settings) - Manage flyctl settings # fly settings analytics disable Source: https://docs.fly.io/flyctl/cmd/fly_settings_analytics_disable Disable analytics ## Usage ``` fly settings analytics disable [flags] ``` ## Options ``` -h, --help help for disable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings analytics](/flyctl/cmd/fly_settings_analytics) - Control anonymized analytics collection # fly settings analytics enable Source: https://docs.fly.io/flyctl/cmd/fly_settings_analytics_enable Enable analytics ## Usage ``` fly settings analytics enable [flags] ``` ## Options ``` -h, --help help for enable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings analytics](/flyctl/cmd/fly_settings_analytics) - Control anonymized analytics collection # fly settings autoupdate Source: https://docs.fly.io/flyctl/cmd/fly_settings_autoupdate Control automatic updates ## Usage ``` fly settings autoupdate [flags] ``` ## Available Commands * [disable](/flyctl/cmd/fly_settings_autoupdate_disable) - Disable automatic updates * [enable](/flyctl/cmd/fly_settings_autoupdate_enable) - Enable automatic updates ## Options ``` -h, --help help for autoupdate ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings](/flyctl/cmd/fly_settings) - Manage flyctl settings # fly settings autoupdate disable Source: https://docs.fly.io/flyctl/cmd/fly_settings_autoupdate_disable Disable automatic updates ## Usage ``` fly settings autoupdate disable [flags] ``` ## Options ``` -h, --help help for disable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings autoupdate](/flyctl/cmd/fly_settings_autoupdate) - Control automatic updates # fly settings autoupdate enable Source: https://docs.fly.io/flyctl/cmd/fly_settings_autoupdate_enable Enable automatic updates ## Usage ``` fly settings autoupdate enable [flags] ``` ## Options ``` -h, --help help for enable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings autoupdate](/flyctl/cmd/fly_settings_autoupdate) - Control automatic updates # fly settings synthetics Source: https://docs.fly.io/flyctl/cmd/fly_settings_synthetics Control synthetics agent execution ## Usage ``` fly settings synthetics [flags] ``` ## Available Commands * [disable](/flyctl/cmd/fly_settings_synthetics_disable) - Disable synthetics * [enable](/flyctl/cmd/fly_settings_synthetics_enable) - Enable synthetics ## Options ``` -h, --help help for synthetics ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings](/flyctl/cmd/fly_settings) - Manage flyctl settings # fly settings synthetics disable Source: https://docs.fly.io/flyctl/cmd/fly_settings_synthetics_disable Disable synthetics ## Usage ``` fly settings synthetics disable [flags] ``` ## Options ``` -h, --help help for disable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings synthetics](/flyctl/cmd/fly_settings_synthetics) - Control synthetics agent execution # fly settings synthetics enable Source: https://docs.fly.io/flyctl/cmd/fly_settings_synthetics_enable Enable synthetics ## Usage ``` fly settings synthetics enable [flags] ``` ## Options ``` -h, --help help for enable ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly settings synthetics](/flyctl/cmd/fly_settings_synthetics) - Control synthetics agent execution # fly sftp Source: https://docs.fly.io/flyctl/cmd/fly_sftp Get or put files from a remote VM. ## Usage ``` fly sftp [command] [flags] ``` ## Available Commands * [find](/flyctl/cmd/fly_sftp_find) - The SFTP FIND command lists files (from an optional root directory) on a remote VM. * [get](/flyctl/cmd/fly_sftp_get) - The SFTP GET retrieves a file from a remote VM. * [put](/flyctl/cmd/fly_sftp_put) - The SFTP PUT uploads a file to a remote VM. * [shell](/flyctl/cmd/fly_sftp_shell) - The SFTP SHELL command brings up an interactive SFTP session to fetch and push files from/to a VM. ## Options ``` -h, --help help for sftp ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly sftp find Source: https://docs.fly.io/flyctl/cmd/fly_sftp_find The SFTP FIND command lists files (from an optional root directory) on a remote VM. ## Usage ``` fly sftp find [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 find --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --group) --pty Allocate a pseudo-terminal (default: on when no command is provided) -q, --quiet Don't print progress indicators for WireGuard -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](/flyctl/cmd/fly_sftp) - Get or put files from a remote VM. # fly sftp get Source: https://docs.fly.io/flyctl/cmd/fly_sftp_get The SFTP GET retrieves a file from a remote VM. ## Usage ``` fly sftp get [local-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 get --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --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") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly sftp](/flyctl/cmd/fly_sftp) - Get or put files from a remote VM. # fly sftp put Source: https://docs.fly.io/flyctl/cmd/fly_sftp_put 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") --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --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](/flyctl/cmd/fly_sftp) - Get or put files from a remote VM. # fly sftp shell Source: https://docs.fly.io/flyctl/cmd/fly_sftp_shell The SFTP SHELL command brings up an interactive SFTP session to fetch and push files from/to a VM. ## Usage ``` fly sftp shell [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 shell --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --group) --pty Allocate a pseudo-terminal (default: on when no command is provided) -q, --quiet Don't print progress indicators for WireGuard -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](/flyctl/cmd/fly_sftp) - Get or put files from a remote VM. # fly ssh Source: https://docs.fly.io/flyctl/cmd/fly_ssh Use SSH to log into or run commands on Machines ## Usage ``` fly ssh [command] [flags] ``` ## Available Commands * [console](/flyctl/cmd/fly_ssh_console) - Connect to a running instance of the current app. * [issue](/flyctl/cmd/fly_ssh_issue) - Issue a new SSH credential * [log](/flyctl/cmd/fly_ssh_log) - Log of all issued SSH certs * [sftp](/flyctl/cmd/fly_ssh_sftp) - Get or put files from a remote VM. ## Options ``` -h, --help help for ssh ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly ssh console Source: https://docs.fly.io/flyctl/cmd/fly_ssh_console Connect to a running instance of the current app. ## Usage ``` fly ssh console [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 console --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --group) --pty Allocate a pseudo-terminal (default: on when no command is provided) -q, --quiet Don't print progress indicators for WireGuard -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](/flyctl/cmd/fly_ssh) - Use SSH to log into or run commands on Machines # fly ssh issue Source: https://docs.fly.io/flyctl/cmd/fly_ssh_issue Issue a new SSH credential. With -agent, populate credential into SSH agent. With -hour, set the number of hours (1-72) for credential validity. ## Usage ``` fly ssh issue [org] [path] [flags] ``` ## Options ``` --agent Add key to SSH agent -d, --dotssh Store keys in ~/.ssh, like normal keys -h, --help help for issue --hours int Expiration, in hours (<72) (default 24) -o, --org string The target Fly.io organization --overwrite Overwrite existing SSH keys in same location, if we generated them -u, --username strings Unix usernames the SSH cert can authenticate as (default [root,fly]) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ssh](/flyctl/cmd/fly_ssh) - Use SSH to log into or run commands on Machines # fly ssh log Source: https://docs.fly.io/flyctl/cmd/fly_ssh_log Log of all issued SSH certs ## Usage ``` fly ssh log [flags] ``` ## Options ``` -h, --help help for log -j, --json JSON output -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 ssh](/flyctl/cmd/fly_ssh) - Use SSH to log into or run commands on Machines # fly ssh sftp Source: https://docs.fly.io/flyctl/cmd/fly_ssh_sftp Get or put files from a remote VM. ## Usage ``` fly ssh sftp [command] [flags] ``` ## Available Commands * [find](/flyctl/cmd/fly_ssh_sftp_find) - The SFTP FIND command lists files (from an optional root directory) on a remote VM. * [get](/flyctl/cmd/fly_ssh_sftp_get) - The SFTP GET retrieves a file from a remote VM. * [put](/flyctl/cmd/fly_ssh_sftp_put) - The SFTP PUT uploads a file to a remote VM. * [shell](/flyctl/cmd/fly_ssh_sftp_shell) - The SFTP SHELL command brings up an interactive SFTP session to fetch and push files from/to a VM. ## Options ``` -h, --help help for sftp ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ssh](/flyctl/cmd/fly_ssh) - Use SSH to log into or run commands on Machines # fly ssh sftp find Source: https://docs.fly.io/flyctl/cmd/fly_ssh_sftp_find The SFTP FIND command lists files (from an optional root directory) on a remote VM. ## Usage ``` fly ssh sftp find [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 find --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --group) --pty Allocate a pseudo-terminal (default: on when no command is provided) -q, --quiet Don't print progress indicators for WireGuard -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](/flyctl/cmd/fly_ssh_sftp) - Get or put files from a remote VM. # fly ssh sftp get Source: https://docs.fly.io/flyctl/cmd/fly_ssh_sftp_get The SFTP GET retrieves a file from a remote VM. ## Usage ``` fly ssh sftp get [local-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 get --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --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") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly ssh sftp](/flyctl/cmd/fly_ssh_sftp) - Get or put files from a remote VM. # fly ssh sftp put Source: https://docs.fly.io/flyctl/cmd/fly_ssh_sftp_put 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") --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --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](/flyctl/cmd/fly_ssh_sftp) - Get or put files from a remote VM. # fly ssh sftp shell Source: https://docs.fly.io/flyctl/cmd/fly_ssh_sftp_shell The SFTP SHELL command brings up an interactive SFTP session to fetch and push files from/to a VM. ## Usage ``` fly ssh sftp shell [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 shell --machine string Run the console in the existing machine with the specified ID --no-container Connect to the machine itself rather than to one of its containers -o, --org string The target Fly.io organization -g, --process-group string The target process group (also --group) --pty Allocate a pseudo-terminal (default: on when no command is provided) -q, --quiet Don't print progress indicators for WireGuard -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](/flyctl/cmd/fly_ssh_sftp) - Get or put files from a remote VM. # fly status Source: https://docs.fly.io/flyctl/cmd/fly_status Show the application's current status including application details, tasks, most recent deployment details and in which regions it is currently allocated. ## Usage ``` fly status [flags] ``` ## Options ``` --all Show completed instances -a, --app string Application name -c, --config string Path to application configuration file --deployment Always show deployment status -h, --help help for status -j, --json JSON output --rate int Refresh Rate for --watch (default 5) --watch Refresh details ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly storage Source: https://docs.fly.io/flyctl/cmd/fly_storage Provision and manage Tigris object storage buckets ## Usage ``` fly storage [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_storage_create) - Provision a Tigris object storage bucket * [dashboard](/flyctl/cmd/fly_storage_dashboard) - Visit the Tigris dashboard * [destroy](/flyctl/cmd/fly_storage_destroy) - Permanently destroy a Tigris object storage bucket * [list](/flyctl/cmd/fly_storage_list) - List your Tigris object storage buckets * [status](/flyctl/cmd/fly_storage_status) - Show details about a Tigris storage bucket * [update](/flyctl/cmd/fly_storage_update) - Update an existing Tigris object storage bucket ## Options ``` -h, --help help for storage ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly storage create Source: https://docs.fly.io/flyctl/cmd/fly_storage_create Provision a Tigris object storage bucket ## Usage ``` fly storage 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 bucket -o, --org string The target Fly.io organization -p, --public Objects in the bucket should be publicly accessible --shadow-access-key string Shadow bucket access key --shadow-endpoint string Shadow bucket endpoint --shadow-name string Shadow bucket name --shadow-region string Shadow bucket region --shadow-secret-key string Shadow bucket secret key --shadow-write-through Write objects through to the shadow bucket -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets # fly storage dashboard Source: https://docs.fly.io/flyctl/cmd/fly_storage_dashboard Visit the Tigris dashboard ## Usage ``` fly storage dashboard [bucket_name] [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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets # fly storage destroy Source: https://docs.fly.io/flyctl/cmd/fly_storage_destroy Permanently destroy a Tigris object storage bucket ## Usage ``` fly storage destroy [storage-bucket-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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets # fly storage list Source: https://docs.fly.io/flyctl/cmd/fly_storage_list List your Tigris object storage buckets ## Usage ``` fly storage list [flags] ``` ## Options ``` -h, --help help for list -o, --org string The target Fly.io organization -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets # fly storage status Source: https://docs.fly.io/flyctl/cmd/fly_storage_status Show details about a Tigris storage bucket ## Usage ``` fly storage 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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets # fly storage update Source: https://docs.fly.io/flyctl/cmd/fly_storage_update Update an existing Tigris object storage bucket ## Usage ``` fly storage update [flags] ``` ## Options ``` -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 -p, --public Objects in the bucket should be publicly accessible --shadow-access-key string Shadow bucket access key --shadow-endpoint string Shadow bucket endpoint --shadow-name string Shadow bucket name --shadow-region string Shadow bucket region --shadow-secret-key string Shadow bucket secret key --shadow-write-through Write objects through to the shadow bucket -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly storage](/flyctl/cmd/fly_storage) - Provision and manage Tigris object storage buckets # fly synthetics Source: https://docs.fly.io/flyctl/cmd/fly_synthetics Synthetic monitoring management. ## Usage ``` fly synthetics [command] [flags] ``` ## Available Commands * [agent](/flyctl/cmd/fly_synthetics_agent) - Runs the Synthetics agent ## Options ``` -h, --help help for synthetics ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly synthetics agent Source: https://docs.fly.io/flyctl/cmd/fly_synthetics_agent Runs the Synthetics agent in the foreground. ## Usage ``` fly synthetics agent [flags] ``` ## Options ``` -h, --help help for agent ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly synthetics](/flyctl/cmd/fly_synthetics) - Synthetic monitoring # fly tokens Source: https://docs.fly.io/flyctl/cmd/fly_tokens Manage Fly.io API tokens ## Usage ``` fly tokens [command] [flags] ``` ## Available Commands * [3p](/flyctl/cmd/fly_tokens_3p) - Manage third-party (3P) caveats for Fly.io API tokens * [attenuate](/flyctl/cmd/fly_tokens_attenuate) - Attenuate Fly.io API tokens * [create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens * [debug](/flyctl/cmd/fly_tokens_debug) - Debug Fly.io API tokens * [list](/flyctl/cmd/fly_tokens_list) - List tokens * [revoke](/flyctl/cmd/fly_tokens_revoke) - Revoke tokens ## Options ``` -h, --help help for tokens ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly tokens 3p Source: https://docs.fly.io/flyctl/cmd/fly_tokens_3p Add, manage, and discharge third-party tokens for a Fly.io API token. The token to be manipulated may either be passed in the -t argument or in FLY\_API\_TOKEN. Third-party caveats rely on a secret shared with between the third party and the author of the caveat. Pass this secret with --secret, --secret-file, or through the TOKEN\_3P\_SHARED\_SECRET variable. ## Usage ``` fly tokens 3p [command] [flags] ``` ## Available Commands * [add](/flyctl/cmd/fly_tokens_3p_add) - Add a third-party caveat * [add-discharge](/flyctl/cmd/fly_tokens_3p_add-discharge) - Tack a discharge token onto a Fly.io API token * [discharge](/flyctl/cmd/fly_tokens_3p_discharge) - Exchange a ticket for the token that discharges a third-party caveat * [ticket](/flyctl/cmd/fly_tokens_3p_ticket) - Retrieve the ticket from an existing third-party caveat ## Options ``` -h, --help help for 3p ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens # fly tokens 3p add Source: https://docs.fly.io/flyctl/cmd/fly_tokens_3p_add Add a caveat to the Fly.io token that requires a third-party service, identified by --location (a URL), to supply a discharge token in order to clear. ## Usage ``` fly tokens 3p add [flags] ``` ## Options ``` -h, --help help for add -l, --location string URL identifying third-party service -S, --secret string (insecure) base64 shared secret for third-party caveat -s, --secret-file string file containing base64 shared secret for third-party caveat ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens 3p](/flyctl/cmd/fly_tokens_3p) - Manage third-party (3P) caveats for Fly.io API tokens # fly tokens 3p add-discharge Source: https://docs.fly.io/flyctl/cmd/fly_tokens_3p_add-discharge Once obtained by exchanging a caveat ticket with a third-party service, add the matching discharge token to the Fly.io API token header, to include it with authentication attempts. ## Usage ``` fly tokens 3p add-discharge [flags] ``` ## Options ``` -d, --discharge string Third-party discharge token -h, --help help for add-discharge ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens 3p](/flyctl/cmd/fly_tokens_3p) - Manage third-party (3P) caveats for Fly.io API tokens # fly tokens 3p discharge Source: https://docs.fly.io/flyctl/cmd/fly_tokens_3p_discharge Given the ticket for a third-party caveat, generate the discharge token that satisfies the caveat. ## Usage ``` fly tokens 3p discharge [flags] ``` ## Options ``` -h, --help help for discharge -l, --location string URL identifying third-party service -S, --secret string (insecure) base64 shared secret for third-party caveat -s, --secret-file string file containing base64 shared secret for third-party caveat --ticket string Third party caveat ticket ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens 3p](/flyctl/cmd/fly_tokens_3p) - Manage third-party (3P) caveats for Fly.io API tokens # fly tokens 3p ticket Source: https://docs.fly.io/flyctl/cmd/fly_tokens_3p_ticket If a third-party caveat tied to the URL at --location is present in the Fly.io API token, retrieve its ticket, so it can be submitted to the service to retrieve a discharge token. ## Usage ``` fly tokens 3p ticket [flags] ``` ## Options ``` -h, --help help for ticket -l, --location string URL identifying third-party service ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens 3p](/flyctl/cmd/fly_tokens_3p) - Manage third-party (3P) caveats for Fly.io API tokens # fly tokens attenuate Source: https://docs.fly.io/flyctl/cmd/fly_tokens_attenuate Attenuate a Fly.io API token by appending caveats to it. The token to be attenuated may either be passed in the -t argument or in FLY\_API\_TOKEN. Caveats must be JSON encoded. See [https://github.com/superfly/macaroon](https://github.com/superfly/macaroon) for details on macaroons and caveats. ## Usage ``` fly tokens attenuate [flags] ``` ## Options ``` -f, --file string Filename to read caveats from. Defaults to stdin -h, --help help for attenuate ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens # fly tokens create Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create Create Fly.io API tokens ## Usage ``` fly tokens create [command] [flags] ``` ## Available Commands * [deploy](/flyctl/cmd/fly_tokens_create_deploy) - Create deploy tokens * [machine-exec](/flyctl/cmd/fly_tokens_create_machine-exec) - Create a machine exec token * [org](/flyctl/cmd/fly_tokens_create_org) - Create org deploy tokens * [readonly](/flyctl/cmd/fly_tokens_create_readonly) - Create read-only org tokens * [ssh](/flyctl/cmd/fly_tokens_create_ssh) - Create token for SSH'ing to a single app * [wireguard](/flyctl/cmd/fly_tokens_create_wireguard) - Create a WireGuard token ## Options ``` -h, --help help for create ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens # fly tokens create deploy Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create_deploy Create an API token limited to managing a single app and its resources. Also available as TOKENS DEPLOY. Tokens are valid for 20 years by default. We recommend using a shorter expiry if practical. ## Usage ``` fly tokens create deploy [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -x, --expiry duration The duration that the token will be valid (default 175200h0m0s) -h, --help help for deploy -j, --json JSON output -n, --name string Token name (default "flyctl deploy token") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens # fly tokens create machine-exec Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create_machine-exec Create an API token that can execute a restricted set of commands on a machine. Commands can be specified on the command line or with the command and command-prefix flags. If no command is provided, all commands are allowed. Tokens are valid for 20 years by default. We recommend using a shorter expiry if practical. ## Usage ``` fly tokens create machine-exec [command...] [flags] ``` ## Options ``` -a, --app string Application name -C, --command strings An allowed command with arguments. This command must match exactly -p, --command-prefix strings An allowed command with arguments. This command must match the prefix of a command -c, --config string Path to application configuration file -x, --expiry duration The duration that the token will be valid (default 175200h0m0s) -h, --help help for machine-exec -j, --json JSON output -n, --name string Token name (default "flyctl machine-exec token") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens # fly tokens create org Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create_org Create an API token limited to managing a single org and its resources. Tokens are valid for 20 years by default. We recommend using a shorter expiry if practical. ## Usage ``` fly tokens create org [flags] ``` ## Options ``` -x, --expiry duration The duration that the token will be valid (default 175200h0m0s) -h, --help help for org -j, --json JSON output -n, --name string Token name (default "Org deploy token") -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 tokens create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens # fly tokens create readonly Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create_readonly Create an API token limited to reading a single org and its resources. Tokens are valid for 20 years by default. We recommend using a shorter expiry if practical. ## Usage ``` fly tokens create readonly [flags] ``` ## Options ``` -x, --expiry duration The duration that the token will be valid (default 175200h0m0s) --from-existing Use an existing token as the basis for the read-only token -h, --help help for readonly -j, --json JSON output -n, --name string Token name (default "Read-only org token") -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 tokens create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens # fly tokens create ssh Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create_ssh Create token for SSH'ing to a single app. To be able to SSH to an app, this token is also allowed to connect to the org's wireguard network. ## Usage ``` fly tokens create ssh [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -x, --expiry duration The duration that the token will be valid (default 175200h0m0s) -h, --help help for ssh -j, --json JSON output -n, --name string Token name (default "flyctl ssh token") ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens # fly tokens create wireguard Source: https://docs.fly.io/flyctl/cmd/fly_tokens_create_wireguard Create an API token limited to WireGuard peer management for an organization. Tokens are valid for 20 years by default. We recommend using a shorter expiry if practical. ## Usage ``` fly tokens create wireguard [flags] ``` ## Options ``` -x, --expiry duration The duration that the token will be valid (default 175200h0m0s) -h, --help help for wireguard -j, --json JSON output -n, --name string Token name (default "WireGuard token") -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 tokens create](/flyctl/cmd/fly_tokens_create) - Create Fly.io API tokens # fly tokens debug Source: https://docs.fly.io/flyctl/cmd/fly_tokens_debug Decode and print a Fly.io API token. The token to be debugged may either be passed in the -t argument or in FLY\_API\_TOKEN. See [https://github.com/superfly/macaroon](https://github.com/superfly/macaroon) for details Fly.io macaroon tokens. ## Usage ``` fly tokens debug [flags] ``` ## Options ``` -f, --file string Filename to read caveats from. Defaults to stdin -h, --help help for debug ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens # fly tokens list Source: https://docs.fly.io/flyctl/cmd/fly_tokens_list List tokens ## Usage ``` fly tokens list [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for list -o, --org string The target Fly.io organization -s, --scope string either 'app' or 'org' ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens # fly tokens revoke Source: https://docs.fly.io/flyctl/cmd/fly_tokens_revoke Revoke one or more tokens. ## Usage ``` fly tokens revoke [flags] ID ID ... ``` ## Available Commands * [supplied](/flyctl/cmd/fly_tokens_revoke_supplied) - Revoke an access token supplied by flag or environment ## Options ``` -h, --help help for revoke ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens](/flyctl/cmd/fly_tokens) - Manage Fly.io API tokens # fly tokens revoke supplied Source: https://docs.fly.io/flyctl/cmd/fly_tokens_revoke_supplied Revoke the access token supplied by --access-token, FLY\_ACCESS\_TOKEN, or FLY\_API\_TOKEN, including all tokens in a token bundle. Uses --access-token first, then FLY\_ACCESS\_TOKEN, then FLY\_API\_TOKEN. An explicitly empty FLY\_ACCESS\_TOKEN prevents FLY\_API\_TOKEN from being selected. Use 'fly auth logout' to revoke the access token saved by 'fly auth login'. Does not clear local configuration or unset environment variables. ## Usage ``` fly tokens revoke supplied [flags] ``` ## Options ``` -h, --help help for supplied ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly tokens revoke](/flyctl/cmd/fly_tokens_revoke) - Revoke tokens # fly version Source: https://docs.fly.io/flyctl/cmd/fly_version Shows version information for the flyctl command itself, including version number and build date. ## Usage ``` fly version [flags] ``` ## Available Commands * [upgrade](/flyctl/cmd/fly_version_upgrade) - Checks for available updates and automatically upgrades ## Options ``` -h, --help help for version -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](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly version upgrade Source: https://docs.fly.io/flyctl/cmd/fly_version_upgrade Checks for an update and if one is available, runs the appropriate command to upgrade the application. ## Usage ``` fly version upgrade [flags] ``` ## Options ``` -h, --help help for upgrade ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly version](/flyctl/cmd/fly_version) - Show version information for the flyctl command # fly volumes Source: https://docs.fly.io/flyctl/cmd/fly_volumes Manage Fly Volumes. Volumes are persistent storage for Fly Machines. Learn how how volumes work: [https://fly.io/docs/volumes/overview/](https://fly.io/docs/volumes/overview/). ## Usage ``` fly volumes [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_volumes_create) - Create a new volume for an app. * [destroy](/flyctl/cmd/fly_volumes_destroy) - Destroy one or more volumes. * [extend](/flyctl/cmd/fly_volumes_extend) - Extend a volume to the specified size. * [fork](/flyctl/cmd/fly_volumes_fork) - Fork the specified volume. * [list](/flyctl/cmd/fly_volumes_list) - List the volumes associated with an app. * [show](/flyctl/cmd/fly_volumes_show) - Show the details of the specified volume. * [snapshots](/flyctl/cmd/fly_volumes_snapshots) - Manage volume snapshots. * [update](/flyctl/cmd/fly_volumes_update) - Update a volume for an app. ## Options ``` -h, --help help for volumes ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly volumes create Source: https://docs.fly.io/flyctl/cmd/fly_volumes_create Create a new volume for an app. Volumes are persistent storage for Fly Machines. Learn how to add a volume to your app: [https://fly.io/docs/launch/volume-storage/](https://fly.io/docs/launch/volume-storage/). ## Usage ``` fly volumes create [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -n, --count int The number of volumes to create (default 1) -h, --help help for create --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) -j, --json JSON output --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') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes destroy Source: https://docs.fly.io/flyctl/cmd/fly_volumes_destroy Destroy one or more volumes. When you destroy a volume, you permanently delete all its data. ## Usage ``` fly volumes destroy ... [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 (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes extend Source: https://docs.fly.io/flyctl/cmd/fly_volumes_extend Extend a volume to the specified size. Most Machines don't require a restart after extending a volume. Some older Machines get a message to manually restart the Machine to increase the size of the file system. ## Usage ``` fly volumes extend [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for extend -j, --json JSON output -s, --size string Target volume size in gigabytes -y, --yes Accept all confirmations (also --auto-confirm) ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes fork Source: https://docs.fly.io/flyctl/cmd/fly_volumes_fork Fork the specified volume. Volume forking creates an independent copy of a storage volume for backup, testing, and experimentation without altering the original data. ## Usage ``` fly volumes fork [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for fork --host-dedication-id string The dedication id of the reserved hosts for your organization (if any) -j, --json JSON output -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') (also --vm-cpukind) --vm-cpus int Number of CPUs (also --cpus) --vm-memory string Memory (in megabytes) to attribute to the VM (also --memory) --vm-size string The VM size to set machines to. See "fly platform vm-sizes" for valid values ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes list Source: https://docs.fly.io/flyctl/cmd/fly_volumes_list List the volumes associated with an app. ## Usage ``` fly volumes list [flags] ``` ## Options ``` --all Show all volumes including those in destroyed states -a, --app string Application name -c, --config string Path to application configuration file -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 volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes show Source: https://docs.fly.io/flyctl/cmd/fly_volumes_show Show the details of the specified volume. ## Usage ``` fly volumes show [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for show -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 volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes snapshots Source: https://docs.fly.io/flyctl/cmd/fly_volumes_snapshots Manage volume snapshots. A snapshot is a point-in-time copy of a volume. Snapshots can be used to create new volumes or restore a volume to a previous state. ## Usage ``` fly volumes snapshots [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_volumes_snapshots_create) - Create a volume snapshot. * [list](/flyctl/cmd/fly_volumes_snapshots_list) - List snapshots. ## Options ``` -h, --help help for snapshots ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly volumes snapshots create Source: https://docs.fly.io/flyctl/cmd/fly_volumes_snapshots_create Snapshot a volume ## Usage ``` fly volumes snapshots create [flags] ``` ## Options ``` -a, --app string Application name -h, --help help for create -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 volumes snapshots](/flyctl/cmd/fly_volumes_snapshots) - Manage volume snapshots. # fly volumes snapshots list Source: https://docs.fly.io/flyctl/cmd/fly_volumes_snapshots_list List snapshots associated with the specified volume. ## Usage ``` fly volumes snapshots list [flags] ``` ## Options ``` -a, --app string Application name -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 volumes snapshots](/flyctl/cmd/fly_volumes_snapshots) - Manage volume snapshots. # fly volumes update Source: https://docs.fly.io/flyctl/cmd/fly_volumes_update Update a volume for an app. Volumes are persistent storage for Fly Machines. ## Usage ``` fly volumes update [flags] ``` ## Options ``` -a, --app string Application name -c, --config string Path to application configuration file -h, --help help for update -j, --json JSON output --scheduled-snapshots Activate/deactivate scheduled automatic snapshots --snapshot-retention int Snapshot retention in days ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly volumes](/flyctl/cmd/fly_volumes) - Manage Fly Volumes. # fly wireguard Source: https://docs.fly.io/flyctl/cmd/fly_wireguard Commands that manage WireGuard peer connections ## Usage ``` fly wireguard [command] [flags] ``` ## Available Commands * [create](/flyctl/cmd/fly_wireguard_create) - Add a WireGuard peer connection * [list](/flyctl/cmd/fly_wireguard_list) - List all WireGuard peer connections * [remove](/flyctl/cmd/fly_wireguard_remove) - Remove a WireGuard peer connection * [reset](/flyctl/cmd/fly_wireguard_reset) - Reset WireGuard peer connection for an organization * [token-mode](/flyctl/cmd/fly_wireguard_token-mode) - Enable or disable token-mode WireGuard peers * [websockets](/flyctl/cmd/fly_wireguard_websockets) - Enable or disable WireGuard tunneling over WebSockets ## Options ``` -h, --help help for wireguard ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly](/flyctl/cmd/fly_help) - The Fly.io command line interface # fly wireguard create Source: https://docs.fly.io/flyctl/cmd/fly_wireguard_create Add a WireGuard peer connection to an organization ## Usage ``` fly wireguard create [org] [region] [name] [file] [flags] ``` ## Options ``` -h, --help help for create --network string Custom network name ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections # fly wireguard list Source: https://docs.fly.io/flyctl/cmd/fly_wireguard_list List all WireGuard peer connections ## Usage ``` fly wireguard list [org] [flags] ``` ## Options ``` -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 wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections # fly wireguard remove Source: https://docs.fly.io/flyctl/cmd/fly_wireguard_remove Remove a WireGuard peer connection from an organization ## Usage ``` fly wireguard remove [org] [name] [flags] ``` ## Options ``` -h, --help help for remove ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections # fly wireguard reset Source: https://docs.fly.io/flyctl/cmd/fly_wireguard_reset Reset WireGuard peer connection for an organization ## Usage ``` fly wireguard reset [org] [flags] ``` ## Options ``` -h, --help help for reset ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections # fly wireguard token-mode Source: https://docs.fly.io/flyctl/cmd/fly_wireguard_token-mode Enable or disable token-mode WireGuard peers. In token mode the agent provisions WireGuard peers inline at the token gateway, authenticating with the session's tokens, instead of registering them through the API. This setting is the local default; the agent also consults the wireguard-token-mode feature flag each time it builds a tunnel, and the flag can turn token mode on for a user whose local setting is off. ## Usage ``` fly wireguard token-mode [enable|disable] [flags] ``` ## Options ``` -h, --help help for token-mode ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections # fly wireguard websockets Source: https://docs.fly.io/flyctl/cmd/fly_wireguard_websockets Enable or disable WireGuard tunneling over WebSockets ## Usage ``` fly wireguard websockets [enable|disable] [flags] ``` ## Options ``` -h, --help help for websockets ``` ## Global Options ``` -t, --access-token string Fly API Access Token --debug Print additional logs and traces --verbose Verbose output ``` ## See Also * [fly wireguard](/flyctl/cmd/fly_wireguard) - Commands that manage WireGuard peer connections # flyctl - The Fly.io CLI Source: https://docs.fly.io/flyctl/index flyctl is the Fly.io CLI. Start here for the core fly commands: auth signup and login, launch, deploy, apps, secrets, logs, status, releases, ips, certs. flyctl, the Fly.io CLI, is one of the primary ways to interact with the Fly.io platform. You'll use the `fly` command to create and deploy apps, manage Machines and volumes, configure networking, and more. First, [install flyctl](/flyctl/install). Also check out [flyctl and continuous integration](/flyctl/integrating) which covers environment variables, JSON and other automation related information about flyctl. The following list includes some important commands to get started. ## Using your Fly.io Account * Signing Up: [fly auth signup](/flyctl/cmd/fly_auth_signup) * Logging In: [fly auth login](/flyctl/cmd/fly_auth_login) ## Working with Apps * Create An App: [fly launch](/flyctl/cmd/fly_launch) * Deploy An App: [fly deploy](/flyctl/cmd/fly_deploy) * Manage Apps: [fly apps](/flyctl/cmd/fly_apps) * Manage App Secrets: [fly secrets](/flyctl/cmd/fly_secrets) ## Viewing and monitoring an App * Show App Logs: [fly logs](/flyctl/cmd/fly_logs) * Show App Deployment Status: [fly status](/flyctl/cmd/fly_status) * Show App Releases: [fly releases](/flyctl/cmd/fly_releases) ## Configuring networking and certificates * Assign IP Addresses: [fly ips](/flyctl/cmd/fly_ips) * Create Certificates: [fly certs](/flyctl/cmd/fly_certs) # Install flyctl Source: https://docs.fly.io/flyctl/install Install flyctl on macOS with Homebrew or the install script, on Linux with curl, or on Windows with PowerShell, and how to uninstall it later. flyctl is a command-line utility that lets you work with Fly.io using `fly` commands, from creating your account to deploying your applications. It runs on your local device so you'll want to install the version that's appropriate for your operating system. ### macOS If you have the [Homebrew](https://brew.sh) package manager installed, flyctl can be installed by running: ```bash theme={null} brew install flyctl ``` If not, you can run the install script: ```bash theme={null} curl -L https://fly.io/install.sh | sh ``` If you used curl to install flyctl, then you need to add the flyctl directory to your shell rc file. Check the output of the install script for the entries to copy and paste into the file. Now you can use the `fly` command from any directory. ### Linux Run the install script: ```bash theme={null} curl -L https://fly.io/install.sh | sh ``` ### Windows Run the PowerShell install script: ```bash theme={null} pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex" ``` If you encounter an error saying the `pwsh` command is not found, `powershell` can be used instead, though we recommend [installing the latest version of PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows). ## Uninstall flyctl How you remove flyctl depends on how you installed it. **Homebrew (macOS):** Uninstall with `brew uninstall flyctl`. **Install script (macOS and Linux):** Remove the flyctl directory (`~/.fly` by default), then delete the lines the install script added to your shell rc file (for example `~/.bashrc` or `~/.zshrc`) to put flyctl on your `PATH`. **Windows (PowerShell):** Remove the flyctl install directory (`~\.fly` by default) and remove its entry from your `PATH`. ### Next steps * [Launch an app](/getting-started/launch) * [Browse what's possible on Fly.io](/blueprints) # Integrating flyctl Source: https://docs.fly.io/flyctl/integrating Run flyctl in CI: FLY_API_TOKEN and scoped deploy tokens, FLY_APP, GitHub Actions for monorepos and staging, --json output with jq, and remote builds. flyctl is designed to work in scripted and automated environments. You can use it in CI/CD pipelines, deployment scripts, and custom tooling with the same commands you run locally. ## Authentication for automation ### FLY\_API\_TOKEN Set the `FLY_API_TOKEN` environment variable to authenticate flyctl without interactive login: ```bash theme={null} export FLY_API_TOKEN="your-token-here" ``` When this variable is set, flyctl skips all interactive auth prompts and uses the token for every command. It takes precedence over the token saved by a prior `fly auth login`. flyctl also honors `FLY_ACCESS_TOKEN` as an alternative name for the same value. If both variables are set, `FLY_ACCESS_TOKEN` takes precedence. If you're testing a token env var locally on a machine where you're also logged in to flyctl, run `fly auth logout` first. This command revokes and removes the token saved by `fly auth login`; it doesn't revoke or unset tokens supplied through environment variables. The env var wins for each command, but mixing it with a saved login can cause confusing behavior: `fly auth whoami` shows a token identity rather than your user, and scope-limited tokens silently filter output from commands like `fly apps list` instead of erroring. In CI, this isn't an issue because there's no saved login. ### Token types Use the narrowest scope that works for your use case. **Deploy token (single app)** This is the default choice for CI deploys: ```bash theme={null} fly tokens create deploy -a my-app -x 720h ``` This token can only deploy and manage the specified app. The `-x` flag sets expiry; `720h` is 30 days. Without `-x`, tokens default to 20 years. **Org deploy token** For multi-app pipelines or org-wide automation: ```bash theme={null} fly tokens create org -o my-org -x 720h ``` Can manage any app in the organization. Use this when a single pipeline deploys multiple apps. **SSH token** For SSH access to a single app's machines: ```bash theme={null} fly tokens create ssh -a my-app -x 24h ``` **Machine exec token** These are restricted to running a specific command: ```bash theme={null} fly tokens create machine-exec -a my-app --command "/app/migrate" -x 1h ``` The token can only execute the specified command. Good for one-off tasks like database migrations in CI. The short form of `--command` is `-C` (uppercase). **Read-only org token** For automation that only needs to read state: ```bash theme={null} fly tokens create readonly -o my-org -x 720h ``` Scoped to the entire org (like `fly tokens create org`) but can't create, deploy, or modify anything. Good for monitoring scripts, status dashboards, or any workflow that only consumes data from the Fly API. ### Token management List active tokens: ```bash theme={null} fly tokens list ``` Revoke one or more tokens by ID (get IDs from `fly tokens list`): ```bash theme={null} fly tokens revoke ``` Revoke a token supplied through `--access-token`, `FLY_ACCESS_TOKEN`, or `FLY_API_TOKEN` without looking up its ID: ```bash theme={null} fly tokens revoke supplied ``` The command revokes the first supplied token according to that order of precedence. It doesn't unset an environment variable or clear your saved login. If both environment variables are set, revoke the `FLY_ACCESS_TOKEN` token first, unset `FLY_ACCESS_TOKEN`, and run the command again to revoke the `FLY_API_TOKEN` token. ### Least-privilege guidance * Use deploy tokens for CI. They can't access other apps or org-level resources. * Use read-only tokens (`fly tokens create readonly`) for any automation that only needs to read state, such as monitoring, status checks, reporting. They can't deploy or modify resources, so they're safe to distribute more widely than deploy tokens. * Set expiry times. Short-lived tokens (`-x 24h`, `-x 720h`) limit the blast radius of a leaked secret. * Rotate tokens on a schedule. Revoke old ones; create new ones. * Never use `fly auth token` in CI, because it returns your full personal token with access to everything. This command is deprecated and hidden from flyctl's help output; use `fly tokens create` instead. ### FLY\_APP Set `FLY_APP` to specify the target app without passing `-a` on every command: ```bash theme={null} export FLY_APP="my-app" fly status # targets my-app fly deploy # deploys my-app ``` This is useful in CI where the app name comes from a pipeline variable or matrix. ## GitHub Actions For a full walkthrough of setting up continuous deployment, see [Continuous deployment with GitHub Actions](/launch/continuous-deployment-with-github-actions). The basics: use the [`superfly/flyctl-actions/setup-flyctl`](https://github.com/superfly/flyctl-actions) action to install flyctl, store your token as a repository secret, and run commands. ### Standard deploy workflow ```yaml theme={null} name: Fly Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest concurrency: deploy-group 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 }} ``` ### Monorepo: deploy multiple apps Use a matrix strategy to deploy several apps from one repository. Each app directory needs its own `fly.toml`. ```yaml theme={null} name: Deploy All Apps on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest strategy: matrix: app: - dir: ./web name: my-web - dir: ./api name: my-api - dir: ./worker name: my-worker concurrency: deploy-${{ matrix.app.name }} defaults: run: working-directory: ${{ matrix.app.dir }} 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 }} FLY_APP: ${{ matrix.app.name }} ``` ### Staging-to-production promotion Deploy to staging on every push. Deploy to production when you create a release. ```yaml theme={null} name: Deploy on: push: branches: [main] release: types: [published] jobs: staging: if: github.event_name == 'push' runs-on: ubuntu-latest concurrency: deploy-staging steps: - uses: actions/checkout@v4 - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy --remote-only -a my-app-staging env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} production: if: github.event_name == 'release' runs-on: ubuntu-latest concurrency: deploy-production steps: - uses: actions/checkout@v4 - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy --remote-only -a my-app env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} ``` ### Running arbitrary flyctl commands You aren't limited to `fly deploy`. Any flyctl command works in Actions: ```yaml theme={null} - run: | flyctl machine list --json flyctl status flyctl machine restart ${{ vars.MACHINE_ID }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} FLY_APP: my-app ``` ## JSON output for scripting Many flyctl commands support `--json` (or `-j`) for machine-readable output. Some commands return a single JSON object; others stream JSON-NL (one JSON object per line). ### Examples Get machine IDs and states: ```bash theme={null} fly status --json | jq '.Machines[] | {id: .id, state: .state}' ``` List only running machines: ```bash theme={null} fly machines list --json | jq '.[] | select(.state == "started")' ``` Get the current image tag: ```bash theme={null} fly status --json | jq -r '.Machines[0].config.image' ``` Count machines by region: ```bash theme={null} fly machines list --json | jq 'group_by(.region) | map({region: .[0].region, count: length})' ``` Use JSON output in a script to wait for a deploy to settle: ```bash theme={null} fly status --json | jq -e '.Machines | all(.state == "started")' > /dev/null ``` The `-e` flag makes `jq` exit with a non-zero status if the expression evaluates to `false` or `null`, which works well in CI checks. ## Remote builds By default, `fly deploy` builds your image remotely on Fly.io infrastructure. You don't need Docker installed locally. ### Build modes **Remote build (default):** ```bash theme={null} fly deploy ``` The first build on a new [builder](/reference/builders) is slower because there's no layer cache. Subsequent builds reuse Docker layers and are significantly faster. **Remote build with the Buildkit builder (experimental):** ```bash theme={null} fly deploy --buildkit ``` Deploys using the buildkit-based remote builder. Often faster than the default, but still experimental. If a build fails with `--buildkit`, try again without the flag before digging into the failure. The classic remote builder is the supported path for now. **Local build:** ```bash theme={null} fly deploy --local-only ``` Builds the image on your machine using your local Docker daemon, then pushes it to the Fly.io registry. Requires Docker to be installed and running. **Pre-built image (skip building entirely):** ```bash theme={null} fly deploy --image registry.example.com/my-app:latest ``` Deploys an existing image directly. Useful when your CI pipeline already builds and pushes images to a registry. ### Notes on caching * The first build on a new builder has no cache and takes longer. * Subsequent builds reuse Docker layers. Structure your Dockerfile to maximize cache hits (dependencies before source code). * If you consistently see slow builds, check the order of your Dockerfile. Copy dependency manifests, install them, *then* copy the rest of your source: ```dockerfile theme={null} COPY package.json package-lock.json ./ RUN npm install COPY . . ``` If the install runs *after* you copy the full source, any source change invalidates the install layer's cache and forces a reinstall on every build, negating the benefit. * Uncompressed images on standard Machines are limited to 8 GB, matching the maximum rootfs size. Larger images can't be deployed, so you'll need to reduce the image size. ## Other CI systems The same patterns work in any CI system. The setup is always: 1. Install flyctl. 2. Set `FLY_API_TOKEN`. 3. Run flyctl commands. ### Install flyctl in CI ```bash theme={null} curl -L https://fly.io/install.sh | sh export PATH="$HOME/.fly/bin:$PATH" ``` This works on any Linux-based CI runner. The script detects the architecture and installs the latest release. ### Generic CI example ```bash theme={null} # Install curl -L https://fly.io/install.sh | sh export PATH="$HOME/.fly/bin:$PATH" # Authenticate via environment export FLY_API_TOKEN="$YOUR_CI_SECRET" # Deploy fly deploy --remote-only -a my-app ``` For GitLab CI, CircleCI, Jenkins, Buildkite, or any other system, the setup is the same. Store your token as a secret in your CI provider, export it as `FLY_API_TOKEN`, and run flyctl commands as you would locally. # Fly.io essentials Source: https://docs.fly.io/getting-started/essentials Learn the core concepts of Fly.io: Fly Machines, Fly Launch, and Fly Apps, how fly launch, fly.toml, fly deploy, and fly scale fit together, plus a glossary. ![Illustration by Annie Ruygt of a figure steering a ship](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/getting-started.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=df4a611f107608b63c07ce8466bcadf3) 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. Two big concepts to know about how Fly.io works, and one small one: Fly Machines, Fly Launch, and Fly Apps. ## Fly Machines: The basic unit of Doing Stuff on Fly.io Fly Machines are fast-launching virtual machines (VMs) that are individually runnable and controllable. We build Machines out of the images you give us and we run them where you tell us to. Machines are the compute on Fly.io,[ our lowest level of orchestration](https://fly.io/blog/carving-the-scheduler-out-of-our-orchestrator/). When you just have to run a VM of a specific size in a specific place, Machines are what you want. If you're thinking about individual VMs, rather than applications comprising groups of VMs, then you're thinking about Machines. Learn more about [Fly Machines](/machines). ## Fly Launch: Manage applications built on Fly Machines Fly Launch is how you manage the whole lifecycle of an application on top of Machines, from starting to scaling to changing and redeploying. If you're not thinking of an individual VM, but rather of an application, like "sandwich-ratings.fly.dev", then you're thinking of Fly Launch. 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`](/reference/configuration) configuration file. * Deploy your app's Machines as a group with the `fly deploy` command. * Scale your app's Machines [horizontally](/launch/scale-count) or [vertically](/launch/scale-machine) with the `fly scale` command. [Scaling based on metrics](/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. Learn more about [Fly Launch](/launch/index). ## Fly Apps: We're not here to tell you what to build Fly Apps are how we group Machines on the Fly.io platform. A Fly App can be a web app, or a database, or a bunch of task Machines, or whatever you want to deploy. When we talk about "your app" in our how-to docs, we're talking about a Fly App. You can manage a Fly App's Machines as a group with Fly Launch features, or you can run and manage individual Machines with the Machines API or with `fly machine` flyctl commands. You can even do all of these things in one Fly App. Learn more about [Fly Apps](/apps/overview). ## Fly.io glossary **The Fly.io platform**: All the primitives, products, and features that make up the Fly.io public cloud. **[Fly Apps](/apps):** The way Machines are grouped for admin and management on the Fly.io platform. **[Fly Launch](/launch/index):** Our orchestrator that includes some good stuff for app hosting, like the `fly launch` command to get started, `fly.toml` for configuration, the `fly deploy` command to deploy all your app's Machines into new versions/releases, and the `fly scale` command to scale Machines. **[Fly Machines](/machines):** [Firecracker microVMs](https://firecracker-microvm.github.io/) that launch quickly in any [region supported by Fly.io](/reference/regions). A VM, or virtual machine, functions like a physical computer, but is software-based. Multiple VMs can run, completely isolated, on one physical host. If you've deployed an app on Fly.io, then it's running on Fly Machines. There’s a fast [REST API](/machines/api) to manage Machines, but you can also use flyctl—the Fly CLI—to manage everything from the command line. And then there’s Fly Launch, which combines flyctl commands with a shared config to manage your app’s Machines as a group. **[Fly Volumes](/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](/security/org-roles-permissions)**: Administrative entities on Fly.io that let you manage billing, control access by adding and removing members, and share app development environments. # Get started with your language or framework Source: https://docs.fly.io/getting-started/get-started-by-framework Pick your stack: Fly Launch scanners detect and deploy Rails, Django, Laravel, Phoenix, Next.js, Go, Rust, .NET, and more, or fall back to any Dockerfile. Fly Launch scanners detect and deploy the following kinds of apps automatically: * [Astro](/js/frameworks/astro) * [Deno](/js/frameworks/deno) * [Django](/django/getting-started) * [Elixir](/elixir/getting-started) * [FastAPI](/python/frameworks/fastapi) * [Flask](/python/frameworks/flask) * [Go](/languages-and-frameworks/golang) * [JavaScript](/js) * [Laravel](/laravel) * [Meteor](/js/frameworks/meteor) * [.NET](/languages-and-frameworks/dotnet) * [NextJS](/js/frameworks/nextjs) * [Nuxt](/js/frameworks/nuxtjs) * [Python](/python) * [Rails](/rails/getting-started) * [RedwoodJS](/js/frameworks/redwood) * [Remix](/js/frameworks/remix) * [Ruby](/languages-and-frameworks/ruby) * [Rust](/rust) * [Static Website](/languages-and-frameworks/static) * [Streamlit](/python/frameworks/streamlit) * [SvelteKit](/js/frameworks/svelte) Don't see your framework or language in the list? Fly Launch can deploy almost any app from a [Dockerfile](/languages-and-frameworks/dockerfile). Learn more about what [Fly Launch](/launch/index) does. ![Illustration by Annie Ruygt of Frankie the Fly.io hot air balloon character with one hand on their hip and the other hand out, palm up, with images floating above the hand representing the Elixir, Ruby, Laravel, and Django programming languages](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/speedrun-guidebooks.webp?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=28b45b3ee787aa2cd3b7f4638617b623) # Getting started Source: https://docs.fly.io/getting-started/index Begin with Fly.io: the quickstart, hellofly demo, deep dive app, framework guides, and essentials, plus links to Fly Launch, Machines, networking, storage. ![Illustration by Annie Ruygt of a bird reading from a book](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/get-started.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=57b10f99d983036d49eef4a2fb45d3ce) Get up and running on Fly.io: * **[Quickstart](/getting-started/launch):** Launch your own app now. * **[Launch hellofly demo app](/getting-started/launch-demo):** Walk through installing the flyctl command-line tool and creating an account, then launch a simple "hellofly" demo app. * **[Deep dive demo](/deep-dive):** get a fully-functioning app running in the first few minutes and explore how the pieces fit together. * **[Choose your language or framework](/getting-started/get-started-by-framework):** Get started on Fly.io with the tech *you* love. * **[Fly.io essentials](/getting-started/essentials):** A primer on Fly Machines and Fly Launch, plus the Fly.io glossary. It's all here. ## Learn more * [Fly Launch](/apps): You've tried the `fly launch` command. Now learn how to use all the Fly Launch features that help you manage and run your apps. * [Going to production checklist](/apps/going-to-production): Considerations for setting up a production environment on Fly.io. * [Fly.io Blueprints](/blueprints): A library of patterns and examples that you can apply in your own projects. * [Databases & Storage](/database-storage-guides): Options for persistent data storage on Fly.io. * [Fly Machines](/machines): Go deeper with our fast-launching VMs, and use them to run your projects and tasks. * [Networking](/networking): How to connect to an app service, use a custom domain, and take advantage of private networking and dynamic request routing. # Quickstart: Launch your app Source: https://docs.fly.io/getting-started/launch Deploy your first app on Fly.io: install flyctl, sign up or log in, then run fly launch, which generates a Dockerfile and config for most frameworks. Welcome to Fly Launch. `fly launch` scans your project and, for most [common languages and frameworks](/getting-started/get-started-by-framework), generates a Dockerfile and configuration for you. You don't need to write a Dockerfile first. If your project already has one, Fly Launch can use it instead. To deploy your app on Fly.io for the first time: > 1. [Install flyctl](/flyctl/install) – the [open-source](https://github.com/superfly/flyctl) Fly.io CLI. > > 2. Create an account with `fly auth signup` or log in with `fly auth login`. > > 3. Run `fly launch` from inside your project source directory to create, configure, and (for most apps) deploy a new application. > > 4. If prompted, run `fly deploy` to deploy your new app (or to redeploy after changes!). ![Illustration by Annie Ruygt of a phoenix jumping with a motor bike](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/moto-jump.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=a7cea651006d8faa34e85012f85d767a) ## Next steps 1. Run `fly status` to show the status of your app and Machines. 2. Run `fly apps open` to open your app in your browser. 3. Learn some [Fly.io essentials](/getting-started/essentials) and continue to [explore what you can do with Fly Launch](/launch/index). ## Grow and scale Check out some of the ways you can increase availability, capacity, and performance with Fly.io: * Follow the blueprint for [extra Machines for more resilient apps](/blueprints/resilient-apps-multiple-machines) * Read up on [App availability and resiliency](/apps/app-availability) * [Autoscale Machines based on load or custom metrics](/reference/autoscaling) * [Scale Machine CPU and RAM](/launch/scale-machine) * [Scale Machine count](/launch/scale-count) If you have questions, need help, or want to talk about what you're building, visit our [community forum](https://community.fly.io). # Launch a demo app Source: https://docs.fly.io/getting-started/launch-demo Deploy the hellofly demo app to Fly.io with flyctl: run fly launch with a prebuilt image, check fly status, open the app, deploy changes, then delete it. ![Illustration by Annie Ruygt of a rocket on the launch pad](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/launch-demo.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=c6b153b6b685df22612ceb08b7fd55ce) 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. ## 1. Install flyctl flyctl is a command-line utility that lets you work with Fly.io using `fly` commands, from creating your account to deploying your applications. It runs on your local device so you'll want to install the version that's appropriate for your operating system. ### macOS If you have the [Homebrew](https://brew.sh) package manager installed, flyctl can be installed by running: ```bash theme={null} brew install flyctl ``` If not, you can run the install script: ```bash theme={null} curl -L https://fly.io/install.sh | sh ``` If you used curl to install flyctl, then you need to add the flyctl directory to your shell rc file. Check the output of the install script for the entries to copy and paste into the file. Now you can use the `fly` command from any directory. ### Linux Run the install script: ```bash theme={null} curl -L https://fly.io/install.sh | sh ``` ### Windows Run the PowerShell install script: ```bash theme={null} pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex" ``` If you encounter an error saying the `pwsh` command is not found, `powershell` can be used instead, though we recommend [installing the latest version of PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows). ## Uninstall flyctl How you remove flyctl depends on how you installed it. **Homebrew (macOS):** Uninstall with `brew uninstall flyctl`. **Install script (macOS and Linux):** Remove the flyctl directory (`~/.fly` by default), then delete the lines the install script added to your shell rc file (for example `~/.bashrc` or `~/.zshrc`) to put flyctl on your `PATH`. **Windows (PowerShell):** Remove the flyctl install directory (`~\.fly` by default) and remove its entry from your `PATH`. ## 2. Sign up or sign in Sign up for an account or [sign in](#sign-in-to-your-fly-io-account) to your existing account. ### Sign up for a Fly.io account If it’s your first time using Fly.io, you’ll need to create an account. To sign up, run: ```bash theme={null} fly auth signup ``` This will take you to the sign-up page where you can use your email address, your Google account, or your GitHub account to create a Fly.io account. When you sign up with GitHub, we'll send you a confirmation email with a link to set a password; you'll need a password so we can actively verify that it's you for some Fly.io operations. > [!NOTE] > > **Note:** At some point, you'll be prompted for credit card payment information. New accounts start on our Pay As You Go plan. See [Pricing](/about/pricing) and [Billing](/about/billing) for more details. After signing up you'll be returned to your command line, ready to use Fly.io. ### Sign in to your Fly.io account If you already have a Fly.io account, all you need to do is sign in with flyctl. To sign in, run: ```bash theme={null} fly auth login ``` When your browser opens to the Fly.io sign-in screen, enter your user name and password to sign in. If you signed up with GitHub, then click `Sign in with GitHub` to sign in. After logging in you'll be returned to your command line, ready to use Fly.io. > [!NOTE] > > Microsoft WSL users may need to run the following command, which creates a symbolic link that allows the browser to open: > > `ln -s /usr/bin/wslview /usr/local/bin/xdg-open` ## 3. Launch the demo app Fly Launch helps you quickly deploy almost any kind of app using a Docker image. For this example, you'll use our pre-built Docker image, `flyio/hellofly:latest`, to create and deploy the demo app. Running `fly launch` to create a new app generates a [`fly.toml` config file](/reference/configuration) with some useful defaults that you can tweak through a web interface before deploying the app. When you run a command post-deploy, flyctl looks for a `fly.toml` file to get the app name and the configuration to apply. To create the demo app, run: ```bash theme={null} fly launch --image flyio/hellofly:latest ``` You'll get a summary of the defaults for your app. For example: ```output theme={null} Using image flyio/hellofly:latest Creating app in /Users/username/my-app-name We're about to launch your app on Fly.io. Here's what you're getting: Organization: MyName (fly launch defaults to the personal org) Name: my-app-name (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? (y/N) ``` Fly launch creates an app name by appending a random name to your project directory name. App names must be globally unique. To change any settings, type `y` at the prompt to open the Fly Launch page, then click **Confirm Settings** to confirm and deploy the demo app. Or just type `n` to accept the default and deploy the app. Example `fly launch` output: ```output theme={null} Created app 'my-app-name' in organization 'personal' Admin URL: https://fly.io/apps/my-app-name Hostname: my-app-name.fly.dev Wrote config file fly.toml Validating /Users/username/my-app-dir/fly.toml Platform: machines ✓ Configuration is valid ==> Building image Searching for image 'flyio/hellofly:latest' remotely... image found: img_z1nr0lpjz9v5q98w Watch your deployment at https://fly.io/apps/my-app-name/monitoring Provisioning ips for my-app-name Dedicated ipv6: 2a09:8280:1::42:a8f4 Shared ipv4: 66.241.124.213 Add a dedicated ipv4 with: fly ips allocate-v4 This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Visit your newly deployed app at https://my-app-name.fly.dev/ ``` ## Check your app's status After the app is deployed, use the `fly status` command to get the basic details about your new app. For example: ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = hellofly Owner = personal Hostname = hellofly.fly.dev Image = flyio/hellofly:latest Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 148e453b7d7289 1 ord started 2023-05-17T17:39:37Z app 5683d311c3228e 1 ord stopped 2023-05-17T17:38:44Z ``` In this example, the app has a DNS hostname of `hellofly.fly.dev`. The app has two Machines running in the ord (Chicago) region. We recommend running at least two Machines to improve availability. ## 5. Visit your app You can connect to your deployed app with the `fly apps open` command, which opens a browser to `https://.fly.dev/` for a secure connection. You've successfully launched your first app on Fly.io! For fun, add `/` to `fly apps open` and your name will be appended to the app's path to add an extra greeting from the hellofly application. ```bash cmd theme={null} fly apps open /fred ``` ```out out theme={null} opening https://hellofly.fly.dev/fred ... ``` You just deployed an app on Fly.io! ## Deploy changes You probably don't need to make any changes to our demo app. But when you do make changes to an app on Fly.io, deploy a new release with: ```bash theme={null} fly deploy ``` Now that you have a working demo app, learn [what you can do with Fly Launch](/launch/index). ## Delete the demo app When you're done playing with the demo app, delete it so you're not using unnecessary resources: ``` fly apps destroy ``` ## Grow and scale Read about some of the ways you can increase availability, capacity, and performance with Fly.io: * Follow the blueprint for [extra Machines for more resilient apps](/blueprints/resilient-apps-multiple-machines) * Read up on [App availability and resiliency](/apps/app-availability) * [Autoscale Machines based on load or custom metrics](/reference/autoscaling) * [Scale Machine CPU and RAM](/launch/scale-machine) * [Scale Machine count](/launch/scale-count) # Migrate from Heroku Source: https://docs.fly.io/getting-started/migrate-from-heroku Migrate a Heroku app to Fly.io: export config vars, run fly launch, move data to Managed Postgres, set up Redis and workers, then move your custom domain. ![Illustration by Annie Ruygt of a bird carrying an app to a balloon who is welcoming the app to join his group](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/migrate.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=4c2799c8c70b0ff6a598c2a3a6d72673) This guide will walk you through migrating your Heroku app to Fly.io. Just follow the steps in order. **What this guide covers:** * Web apps in any language (Node.js, Python, Ruby, Go, etc.) * Postgres databases * Redis * Background workers * Environment variables * Custom domains **Time required:** Most apps can be migrated in under 30 minutes. Unlike Heroku's dyno-based tiers, Fly.io uses pay-as-you-go pricing - you only pay for the compute, storage, and bandwidth you actually use. There are no fixed plan tiers to choose from. See [fly.io/pricing](https://fly.io/pricing) for current rates. ## TL;DR - The Fast Path For experienced developers, here's the entire migration in 7 commands: ```bash theme={null} # 1. Export secrets (excluding Heroku-managed vars) heroku config -s -a YOUR_HEROKU_APP | grep -v '^DATABASE_URL\|^REDIS_URL\|^HEROKU_' | sed "s/='\(.*\)'$/=\"\1\"/" > secrets.txt # ⚠️ Don't commit secrets.txt to git! # 2. Create Fly app (generates Dockerfile automatically) cd your-app-directory fly launch --no-deploy # 3. Create Managed Postgres and import data fly mpg create --name your-db --region iad # ⚠️ Replace CLUSTER_ID below with the cluster ID from the output above. # If you missed it, run: fly mpg list fly mpg proxy CLUSTER_ID --local-port 15432 & pg_dump --no-owner --no-acl "$(heroku config:get DATABASE_URL -a YOUR_HEROKU_APP)" | \ PGPASSWORD=YOUR_FLY_DB_PASSWORD psql -h localhost -p 15432 -U fly-user -d fly-db fly mpg attach CLUSTER_ID -a your-fly-app # 4. Import secrets cat secrets.txt | fly secrets import # 5. Deploy fly deploy # 6. Verify curl https://your-fly-app.fly.dev ``` For a more detailed explanation, read on. ## Prerequisites Install both CLIs: ```bash theme={null} # Install Fly CLI curl -fsSL https://fly.io/install.sh | sh # Sign up / log in to Fly fly auth signup # or: fly auth login # Make sure you're logged into Heroku heroku login ``` ## Step 1: Export Your Heroku Configuration First, let's capture everything about your Heroku app. ```bash theme={null} # Set your Heroku app name export HEROKU_APP="your-app-name" # Export environment variables (excluding Heroku-managed ones) heroku config -s -a $HEROKU_APP | grep -v '^HEROKU_' | grep -v '^DATABASE_URL' | grep -v '^REDIS_URL' | sed "s/='\(.*\)'$/=\"\1\"/" > heroku-env.txt # ⚠️ This file contains secrets - don't commit it to git! # See what add-ons you have heroku addons -a $HEROKU_APP # Check your current process types heroku ps -a $HEROKU_APP ``` Take note of: * Your add-ons (Postgres, Redis, etc.) * Your dyno types (web, worker, etc.) ## Step 2: Prepare Your App Make sure you have your app's source code locally: ```bash theme={null} # If you don't have it locally, clone from Heroku heroku git:clone -a $HEROKU_APP cd $HEROKU_APP ``` ### Check for a Dockerfile Fly works best with a Dockerfile. If you don't have one, that's fine - `fly launch` will generate one for you based on your app type. ### Check your Procfile Your Heroku `Procfile` maps directly to Fly processes. A typical Procfile: ```procfile theme={null} web: npm start worker: npm run worker release: npm run db:migrate ``` ## Step 3: Launch on Fly From your app directory: ```bash theme={null} fly launch ``` This will: 1. Detect your app type (Node, Python, Rails, etc.) 2. Generate a Dockerfile if needed 3. Create a `fly.toml` configuration file 4. Ask if you want to provision a Postgres database **When prompted:** * Choose a name for your app (or accept the generated one) * Select your preferred region * Say **Yes** to Postgres if you need a database (choose "Managed Postgres" for the fully-managed option) * Say **No** to deploy now - we'll do that after setting up secrets ## Step 4: Set Up Your Database If you have Heroku Postgres, create a Managed Postgres cluster on Fly: ```bash theme={null} fly mpg create --name your-app-db --region iad ``` Save the connection details it outputs! You'll see something like: ``` Connection string: postgresql://fly-user:PASSWORD@pgbouncer.CLUSTER_ID.flympg.net/fly-db ``` Note the cluster ID (the `CLUSTER_ID` part) - you'll need it for the next few commands. If you lose it, you can always find it with `fly mpg list`. To import your data, use `fly mpg proxy` to create a local tunnel, then `pg_dump` and `psql`: ```bash theme={null} # Terminal 1: Start a proxy to your Fly database (replace CLUSTER_ID with your cluster ID) fly mpg proxy CLUSTER_ID --local-port 15432 # Terminal 2: Dump from Heroku and restore to Fly export HEROKU_DB=$(heroku config:get DATABASE_URL -a $HEROKU_APP) # Dump the database (plain SQL format) pg_dump --no-owner --no-acl "$HEROKU_DB" > heroku-dump.sql # Restore to Fly (via the proxy) PGPASSWORD=YOUR_PASSWORD psql -h localhost -p 15432 -U fly-user -d fly-db -f heroku-dump.sql ``` You'll see some errors about Heroku-specific extensions (pg\_stat\_statements, event triggers) - these are safe to ignore. Your actual data will import successfully. Attach the database to your app: ```bash theme={null} # Replace CLUSTER_ID with the cluster ID from fly mpg create (or run fly mpg list) fly mpg attach CLUSTER_ID -a your-app-name ``` This automatically sets `DATABASE_URL` as a secret on your app. ## Step 5: Set Up Redis (if needed) If you use Heroku Redis or Redis Cloud: ```bash theme={null} fly redis create ``` Follow the prompts. This uses Upstash Redis which is fully managed. After creation, attach it to your app: ```bash theme={null} # The create command outputs a REDIS_URL - set it as a secret fly secrets set REDIS_URL="redis://default:xxxxx@fly-your-redis.upstash.io:6379" -a your-app-name ``` **Note:** Fly Redis doesn't support importing existing data. For most use cases (caching, sessions), this is fine - the cache will warm up naturally. ## Step 6: Import Environment Variables Remember that `heroku-env.txt` file we created? Let's import it: ```bash theme={null} # Import all your Heroku environment variables cat heroku-env.txt | fly secrets import -a your-app-name ``` Double-check that secrets were imported: ```bash theme={null} fly secrets list -a your-app-name ``` ## Step 7: Configure Processes (Web + Workers) If your app has multiple process types (web + worker), edit your `fly.toml`: ```toml theme={null} [processes] web = "npm start" worker = "npm run worker" # The web process gets the HTTP service [http_service] internal_port = 8080 processes = ["web"] ``` For a release command (like database migrations), add: ```toml theme={null} [deploy] release_command = "npm run db:migrate" ``` ## Step 8: Deploy ```bash theme={null} fly deploy ``` Watch the deployment. If something fails, check the logs: ```bash theme={null} fly logs ``` ## Step 9: Verify Your App ```bash theme={null} # Open your app in a browser fly apps open # Check the status fly status # View live logs fly logs ``` ## Step 10: Migrate Your Domain If you have a custom domain on Heroku: ```bash theme={null} # Add your domain to Fly fly certs add yourdomain.com -a your-app-name # Get the IP addresses to point your DNS to fly ips list -a your-app-name ``` Update your DNS: * For apex domains (example.com): Create an A record pointing to the IPv4 address * For subdomains ([www.example.com](http://www.example.com)): Create a CNAME pointing to `your-app-name.fly.dev` Once DNS propagates, Fly automatically provisions an SSL certificate. ## Quick Reference: Command Mapping | Heroku | Fly | | - | - | | `heroku apps` | `fly apps list` | | `heroku logs -t` | `fly logs` | | `heroku ps` | `fly status` | | `heroku config` | `fly secrets list` | | `heroku config:set KEY=value` | `fly secrets set KEY=value` | | `heroku run bash` | `fly ssh console` | | `heroku run rails console` | `fly ssh console -C "rails console"` | | `heroku pg:psql` | `fly mpg connect CLUSTER_ID` | | `heroku restart` | `fly apps restart` | | `heroku scale web=2` | `fly scale count web=2` | ## Common Add-on Replacements | Heroku Add-on | Fly Equivalent | | - | - | | Heroku Postgres | `fly mpg create` (Managed Postgres) | | Heroku Redis / Redis Cloud | `fly redis create` (Upstash) | | Heroku Scheduler | Use cron in your app, or a separate "cron" process | | Papertrail | `fly logs` built-in, or add a log drain | | SendGrid | Keep using SendGrid (just use API key) | | New Relic | Keep using New Relic (just use license key) | | S3 / Bucketeer | `fly storage create` (Tigris) or keep using S3 | | Elasticsearch | Deploy Elasticsearch as a Fly app, or use a hosted service | | Memcached | Use Redis instead (`fly redis create`) | ## Multiple Environments (Staging/Production) Fly doesn't have "pipelines" like Heroku. Instead, create separate apps: ```bash theme={null} # Production fly launch --name myapp # Staging fly launch --name myapp-staging ``` For CI/CD, use GitHub Actions with the [Fly GitHub Action](/launch/continuous-deployment-with-github-actions). ## Shutting Down Heroku Once everything is working on Fly: 1. Update your DNS to point to Fly 2. Wait for DNS propagation (check with `dig yourdomain.com`) 3. Monitor both apps for a day to ensure no traffic goes to Heroku 4. Scale down Heroku dynos: `heroku ps:scale web=0 worker=0 -a $HEROKU_APP` 5. Cancel Heroku add-ons 6. Delete the Heroku app: `heroku apps:destroy -a $HEROKU_APP` ## Getting Help * [Fly Community](https://community.fly.io) * [Fly Docs](https://fly.io/docs) * [Fly Status](https://status.fly.io) # Sign up and sign in to Fly.io Source: https://docs.fly.io/getting-started/sign-up-sign-in Sign up for Fly.io with fly auth signup using email, Google, or GitHub, then sign in from your terminal with fly auth login, plus a browser fix for WSL users. ## No account? Sign up for Fly.io If it’s your first time using Fly.io, you’ll need to create an account. To sign up, run: ```bash theme={null} fly auth signup ``` This will take you to the sign-up page where you can use your email address, your Google account, or your GitHub account to create a Fly.io account. When you sign up with GitHub, we'll send you a confirmation email with a link to set a password; you'll need a password so we can actively verify that it's you for some Fly.io operations. > [!NOTE] > > **Note:** At some point, you'll be prompted for credit card payment information. New accounts start on our Pay As You Go plan. See [Pricing](/about/pricing) and [Billing](/about/billing) for more details. After signing up you'll be returned to your command line, ready to use Fly.io. ## Already have a Fly.io account? Sign in If you already have a Fly.io account, all you need to do is sign in with flyctl. To sign in, run: ```bash theme={null} fly auth login ``` When your browser opens to the Fly.io sign-in screen, enter your user name and password to sign in. If you signed up with GitHub, then click `Sign in with GitHub` to sign in. After logging in you'll be returned to your command line, ready to use Fly.io. > [!NOTE] > > Microsoft WSL users may need to run the following command, which creates a symbolic link that allows the browser to open: > > `ln -s /usr/bin/wslview /usr/local/bin/xdg-open` # Troubleshoot your deployment Source: https://docs.fly.io/getting-started/troubleshooting Fix common Fly.io deployment problems: 502 and 503 errors, failed builds, wrong listen addresses, failing health checks, stuck Machines, and networking issues. ![Illustration by Annie Ruygt of a figure looking through a magnifying glass at a balloon](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/troubleshooting.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=d8d76c7ea0a6fa41fec70614705514ab) This page covers the most common problems people hit on Fly.io and how to fix them. If your problem isn't here, check the [community forum](https://community.fly.io/). ## Try this first If the error isn't obvious, start here. * **Update flyctl:** `fly version update` — outdated versions cause weird failures. * **Run diagnostics:** `fly doctor` — checks WireGuard, IPs, and Docker. * **Review fly.toml:** Run `fly config validate` to catch syntax and configuration errors. Double-check formatting, [port numbers](#your-app-isnt-listening-on-the-right-address), and recent changes against the [configuration reference](/reference/configuration). * **Check logs:** `fly logs` in one terminal while running your command in another. For more detail: `LOG_LEVEL=debug fly deploy`. * **SSH in:** `fly ssh console` (use `-s` to pick a specific Machine). ## Find your problem * **I'm getting an error code** * [502 Bad Gateway](#502-bad-gateway) — app didn't respond to the proxy * [503 Service Unavailable](#503-service-unavailable) — no healthy Machines * [401 Unauthorized](#registry-401-errors) — registry auth failure during deploy * [403 Forbidden](#403-forbidden) — usually your app's CORS config or a third-party block * [520 with Cloudflare](#520-errors-with-cloudflare) — Cloudflare doesn't like the response * **My deploy failed** * [Build hangs at 'Waiting for depot builder...'](#build-hangs-waiting-for-depot-builder) * [Release command failures](#release-command-failures) * [Container registry rate limits](#container-registry-rate-limits) * ["App is not listening on the expected address"](#your-app-isnt-listening-on-the-right-address) * [Missing secrets or env vars](#missing-secrets-or-environment-variables) * [Image too large](#image-size-limit) * [Health checks failing on deploy](#health-checks-failing) * **My app is slow or timing out** * [Cold starts after deploy or wake-up](#cold-starts) * [Health check grace period too short](#grace-period) * [Out of memory or high CPU](#out-of-memory-or-high-cpu) * [Suspend vs stop (clock skew)](#suspend-vs-stop) * **I can't connect to something** * [Custom domain TLS errors](#custom-domains-and-tls) * [Flycast redirect loops or TCP issues](#flycast-internal-load-balancing) * [Outbound TCP connections failing](#outbound-connections) * [POST requests getting 403'd](#cors-issues) * [Can't reach Managed Postgres externally](#managed-postgres) * [Redis/Valkey connection errors (IPv6)](#redis-and-valkey) * [Volume/disk corruption](#volumes-and-disk-errors) * **My Machine is stuck or behaving unexpectedly** * [Machine stuck in a state](#stuck-machines) * [Machine stops immediately after starting](#machines-stop-immediately-after-starting) * [Suspend vs stop tradeoffs](#suspend-vs-stop) * [What the /init process does](#the-init-process) * **I can't access the dashboard or my account** * [GitHub SSO issues](#cant-log-in-github-sso) * [Token problems](#token-issues) * **My app is down in a specific region** * [Regional issues and mitigation](#regional-issues) *** ## Error codes ### 502 Bad Gateway The Fly proxy reached your Machine, but your app didn't respond correctly. Common causes: * Your app crashed mid-request * Your app is [listening on the wrong port](#your-app-isnt-listening-on-the-right-address) * The Machine is mid-deploy and your app isn't ready yet Check `fly logs` first. If you see 502s right after deploy, your app probably needs more startup time — increase your [health check grace period](#grace-period). If it's intermittent, check for OOM kills in your logs or SSH in and check memory usage with `free` or `htop.` OOM kills look like crashes to the proxy. If your Machine is running out of memory, [add more RAM](#out-of-memory-or-high-cpu). ### 503 Service Unavailable No healthy Machines are available. Either all your Machines are stopped, they're failing health checks, or there's a [regional issue](#regional-issues). ``` fly status ``` If Machines show `started` but you're still getting 503s, health checks are probably failing. Run `fly checks list` to see health check results. Errors like "connection refused" won't appear in `fly logs`. Try checking both. See [Health checks failing](#health-checks-failing). If all Machines are stopped and you expect auto-start to wake them, verify your `[[services]]` or `[http_service]` config — auto-start only works when the proxy knows where to route traffic. See [Autostart and autostop](/launch/autostop-autostart). ### Registry 401 errors ``` failed to push registry: 401 Unauthorized ``` This shows up during `fly deploy`. Two possible causes: 1. **Your auth token is stale.** Fix it: ``` fly auth logout fly auth login ``` 1. **A Fly registry incident.** Check [status.flyio.net](https://status.flyio.net). If there's an active incident, wait it out or subscribe for updates. Note the image size limit of **8GB**. If your image exceeds this limit, the push fails. ### 403 Forbidden A 403 can come from different places: **From your app's CORS configuration:** The Fly proxy does not enforce CORS or act as a WAF. If you're seeing `403 Invalid CORS request`, that's coming from your application's CORS middleware, not from Fly. Check your app's CORS configuration and make sure the `Origin` header your client sends is in your allowed origins list. **From third-party APIs (outbound):** If your app calls external APIs and gets 403s, the third party may be blocking Fly's IP ranges. This is common with Cloudflare-protected services. Fix: allocate an app-scoped egress IP with `fly ips allocate-egress` so your outbound traffic comes from a consistent IP you can allowlist, or contact the third-party service. You can read more about [app-scoped egress IPs](/networking/egress-ips#static-egress-ips-app-scoped), as well as [some caveats](/networking/egress-ips#caveats). **From object storage:** S3-compatible storage returns 403 on permission issues. Double-check your bucket policy, access keys, and region configuration. ### 520 errors with Cloudflare 520 is a Cloudflare-specific code: "web server returned an unexpected response." When using Cloudflare in front of Fly, this usually means Fly's proxy sent a response header that Cloudflare doesn't understand. The `TE: trailers` header is a known culprit. If you're using Cloudflare: * Set SSL mode to **Full (strict)** * Check your [Cloudflare](/networking/understanding-cloudflare) proxy settings * If 520s are intermittent, they may correlate with specific response headers from your app Note: if Cloudflare itself goes down, your Fly-hosted apps behind Cloudflare go down too. Fly is still running — the CDN in front of it isn't. *** ## Deployment failures ### Build hangs: Waiting for depot builder... Remote builds use Depot. When Depot is having issues, `fly deploy` hangs. Quick fix — switch to the legacy remote builder: ``` fly deploy --depot=false ``` This bypasses Depot but still builds remotely. If remote builds are down entirely, build on your own machine: ``` fly deploy --local-only ``` This requires Docker installed locally. Slower on upload, but doesn't depend on any remote build infrastructure. If your build fails with `exit code: 1`, that's your Dockerfile failing — not a Fly problem. Debug it locally: ``` docker build . ``` ### Release command failures [Release commands](/reference/configuration#the-deploy-section) run in an ephemeral Machine before your app starts. ``` error running release_command machine: machine not found ``` This is usually a platform timing issue. Retry the deploy. If it persists, check `fly logs` to see why the release command Machine is exiting early. ``` failed to get manifest ``` The image hasn't propagated to the registry yet. This happens with two-stage deploys (build + push in one command, deploy in another). Wait about a minute between stages, or retry. ### Container registry rate limits ``` too many requests ``` Fly has a caching proxy for Docker Hub pulls, so Docker Hub rate limits rarely affect builds. However, images hosted on other registries (like GitHub Container Registry) don't go through this cache and can hit rate limits. Options: * Build locally: `fly deploy --local-only` * Use the legacy builder: `fly deploy --depot=false` * Push your image to a private registry or Docker Hub (which benefits from the cache), then deploy with `fly deploy --image ` ### Missing secrets or environment variables If your app crashes on startup complaining about missing config: ``` fly secrets list fly config env ``` Secrets set with `fly secrets set` are available as environment variables at runtime. They're not available at build time. If you need build-time values, use `[build.args]` in `fly.toml`. Find out more about build-time secrets [here](/apps/build-secrets). ### Image size limit Machines have an **8GB rootfs limit**. If your image is too large: * Use multi-stage Docker builds to drop build dependencies * Move large assets to a [volume](/volumes) or object storage * Check for accidentally included files — add a `.dockerignore` ### Buildpack deploys Buildpacks work but Dockerfiles are more reliable and give you more control. If you're hitting buildpack issues, consider switching. The `fly launch` command generates a Dockerfile for most frameworks. *** ## Your app isn't listening on the right address You'll see this during deploy: ``` WARNING The app is not listening on the expected address and will not be reachable by fly-proxy. ``` Your app must listen on `0.0.0.0` (not `localhost`, not `127.0.0.1`) on the port specified by `internal_port` in your `fly.toml`. If your `fly.toml` says: ``` [http_service] internal_port = 8080 ``` Then your app must listen on `0.0.0.0:8080`. **Common mistakes:** * Listening on `127.0.0.1` or `localhost` — this only accepts connections from inside the Machine. The Fly proxy connects from outside, so it can't reach your app. Some frameworks (Rails, Django, Next.js) default to localhost. Set the host to `0.0.0.0` explicitly. * Port mismatch — your app listens on 3000, but `internal_port` is 8080. Pick one and make them match. **Framework examples:** Rails: ``` bin/rails server -b 0.0.0.0 -p 8080 ``` Express / Fastify (Node.js): ```javascript theme={null} // Express app.listen(8080, '0.0.0.0') // Fastify fastify.listen({ port: 8080, host: '0.0.0.0' }) ``` Flask / Django (via Gunicorn): ``` gunicorn --bind 0.0.0.0:8080 myapp:app ``` Don't use Flask's or Django's built-in dev servers in production. Use Gunicorn or another WSGI server. FastAPI (Uvicorn): ``` uvicorn main:app --host 0.0.0.0 --port 8080 ``` *** ## Health checks failing Health checks tell the Fly proxy whether your Machine is ready to receive traffic. If a Machine fails its health checks, the proxy stops routing requests to it. If all your Machines fail health checks, your users get 503s. For the full picture on how health checks work, see [Health checks](/reference/health-checks). ### Out of memory or high CPU If your app OOMs, the Machine crashes and health checks fail by definition. ``` fly machine status ``` Look for OOM kill events. Fix: add memory. ``` fly scale memory 512 ``` For CPU-intensive apps, make sure you've selected an appropriate [Machine size](/about/pricing#started-fly-machines). CPU and RAM scale together in preset combinations. ### Grace period Your app needs time to start before health checks begin. Failed health checks are retried, but each failure adds backoff before the next attempt. If your app takes too long to become healthy, the deploy can fail. Set a grace period to delay the first check: ``` [[services.tcp_checks]] grace_period = "10s" ``` For apps with slow startup (Rails, Django, large JVM apps), you may need 15-30 seconds. If you're not sure, start with `10s` and increase if deploys keep failing. ### Other health check failures * **Blocked accept loop:** Your app's main thread is busy and can't accept new connections. Offload CPU work to background threads/workers. * **Non-200 responses:** HTTP health checks expect a 200. If your health check endpoint returns redirects, auth challenges, or errors, the check fails. Use a dedicated `/healthz` endpoint that always returns 200. * **App panics on startup:** Check `fly logs` for stack traces. Fix the crash. If it only happens on Fly (not locally), check your [secrets and env vars](#missing-secrets-or-environment-variables). Define an explicit HTTP health check rather than relying on the implicit one: ``` [[services.http_checks]] grace_period = "10s" interval = "15s" method = "GET" path = "/healthz" timeout = "5s" ``` *** ## Cold starts After a deploy or when a stopped Machine wakes up, the first request is slow. This is expected — the Machine needs to boot and your app needs to initialize. **Reduce cold start impact:** * **Set a grace period** on your health check so the proxy waits for your app. See [Grace period](#grace-period). * **Keep a Machine warm** with `min_machines_running = 1` in your `[http_service]` config. This ensures at least one machine is always running. * **Use `stop` instead of `suspend`** if cold start latency matters more than wake-up speed. `suspend` is faster to resume but has [clock issues](#suspend-vs-stop). * **Lighten your startup.** For heavy frameworks, defer non-essential initialization. Make your health check endpoint respond before the full app is ready. If the first request after deploy always fails (not just slow), your grace period is probably too short. The proxy sends the request, your app isn't ready, and the request times out. *** ## Machine lifecycle issues ### Stuck Machines Machines occasionally get stuck in a state (`replacing`, `starting`, `created`) and stop responding to commands. Try these in order: 1. **Restart it:** ``` fly machine restart ``` 1. **Force an update** (any metadata change can unstick the platform state):: ``` fly machine update --yes --metadata foo=bar ``` 1. **Force destroy** (nuclear option — destroys the Machine): ``` fly machine destroy --force ``` After force-destroying, scale back up to replace it: ``` fly scale count ``` ### Machines stop immediately after starting If your Machine starts and immediately stops, your app's process is exiting. The Machine has nothing to run, so it shuts down. * Make sure your Dockerfile has an explicit `CMD`. Don't rely on the base image default. * Test locally: `docker run `. If it exits immediately in Docker, it'll exit immediately on Fly. * Check `fly logs` for your app's exit code and any error output. ### Suspend vs stop `stop` shuts down the VM. `suspend` snapshots memory to disk and resumes later — faster wake-up, but with a tradeoff. **The clock problem:** When a Machine resumes from suspend, the system clock is wrong for a brief period. It thinks it's still the time when the Machine was suspended. This breaks: * **JWT validation** — tokens appear to be issued in the future (`nbf` claim fails) * **Cron jobs** — scheduled tasks fire at the wrong time * **Cache TTLs** — expiration times are off * **TLS certificate validation** — cert timestamps don't match The clock corrects itself quickly, but if your app checks timestamps during the first moments after resume, things break. **Fix:** If your app uses JWTs, time-sensitive scheduling, or certificate validation on startup, use `stop` instead of `suspend`: ``` [http_service] auto_stop_machines = "stop" ``` Or add clock-skew tolerance to your JWT validation (a few seconds of leeway). *** ## The init process Fly injects a lightweight `init` process at runtime when your Machine starts. It doesn't modify your image — it runs in front of your app inside the VM. This init handles: * Reaping orphaned child processes (PID 1 responsibilities) * Forwarding signals from the host to your app * Setting up networking and volume mounts * Coordinating clean shutdowns **You don't need `tini`, `dumb-init`, or `s6-overlay` in your Dockerfile.** Fly's init covers these responsibilities. It's not a problem to keep them if they're already there — they'll just be redundant. You can't disable or replace Fly's init. If you need setup scripts before your app starts, use a Docker `ENTRYPOINT` script that runs your setup and then execs your app. *** ## Networking and connectivity ### Custom domains and TLS If your custom domain shows TLS errors, do an active check: ``` fly certs show ``` You need either A **and** AAAA records, or a single CNAME record pointing to Fly (don't mix CNAME with A/AAAA) **Using Cloudflare?** Most TLS issues on Fly involve domains behind Cloudflare. Read [Understanding Cloudflare](/networking/understanding-cloudflare) before debugging further. For all other setups, see [Custom domains](/networking/custom-domain). ### Flycast (internal load balancing) [Flycast](/networking/flycast) routes traffic between your Fly apps over the private network. Two gotchas: **force\_https must be false.** Flycast is HTTP-only. Don't use `force_https`: ``` # Wrong for Flycast [http_service] force_https = true # Right for Flycast [http_service] force_https = false ``` **Plain TCP services** need `[[services]]` with `protocol = "tcp"`, not `[http_service]`: ``` [[services]] internal_port = 8080 protocol = "tcp" [[services.ports]] handlers = [] port = 4321 ``` ### Outbound connections **Raw TCP over shared IPv4 doesn't work.** Fly's shared IPv4 addresses use the proxy, which needs SNI (from TLS) or a `Host` header (from HTTP) to route virtual host traffic. Non-HTTP, non-TLS TCP connections, such as unencrypted Redis, SMTP on port 25, or raw socket connections, fail on shared IPs because the proxy can't identify which app to route to. Fixes: * Allocate a dedicated IPv4: `fly ips allocate-v4`— gives your app its own IP, no virtual host routing needed * Use `.internal` addresses for services on Fly's private network — these bypass the proxy entirely **SMTP:** If you're having trouble with outbound email, we recommend using a transactional email service (like Postmark, Resend, or SendGrid) rather than sending directly from your Machines. ### CORS issues If POST requests to your app return `403`, it's almost certainly your app's CORS middleware. The Fly proxy does not have a WAF and does not enforce CORS. * Check that the `Origin` header your client sends is in your app's allowed origins list * Make sure your app returns the correct `Access-Control-Allow-Origin`0 headers on preflight (`OPTIONS`) responses * If it works via `curl` but fails in the browser, that confirms it's a CORS issue in your app, not a Fly issue *** ## Database connections ### Managed Postgres MPG clusters run on Fly's private network and aren't accessible over the public internet. Connection strings use `.flympg.net` domains, which resolve to private network addresses. See [Create and connect to MPG](/postgres/create-and-connect) for full details. **To connect from your local machine::** * Interactive psql: `fly mpg connect` * Proxy to localhost: `fly mpg proxy` — forwards a local port to your database * WireGuard: connect to your org's private network, then use the `.flympg.net` connection string directly. Read more in this [reference guide](/blueprints/connect-private-network-wireguard). If `fly mpg proxy` times out, try `fly mpg connect` first to verify the cluster is healthy. ### Redis and Valkey **IPv6 is required on Fly's private network.** Most Redis clients default to IPv4. If your connection fails with I/O errors: ```javascript theme={null} // ioredis — set family: 6 const redis = new Redis(process.env.REDIS_URL, { family: 6, maxRetriesPerRequest: null, enableReadyCheck: false, }); ``` For Upstash Redis on Fly, use the internal endpoint over IPv6, not the public TLS endpoint. ### Volumes and disk errors If you see filesystem errors like `unable to read superblock`, your volume is corrupted. This is rare but can happen after a hard crash. If you have snapshots enabled: ``` fly volumes list fly volumes snapshots list fly volumes create --snapshot-id --region ``` If you don't have snapshots, the data may be unrecoverable. **Always enable snapshots for volumes with data you care about.** See [Volume snapshots](/volumes/snapshots). *** ## Dashboard and account access ### Can't log in (GitHub SSO) If GitHub SSO stops working and you can't access the dashboard: * Try `fly auth logout` then `fly auth login` from the CLI * If you need SSO removed from your account, email **[billing@fly.io](mailto:billing@fly.io)** — they verify ownership before making SSO changes ### Token issues If you can't create or manage tokens: ``` fly tokens list ``` Token management bugs occasionally appear in specific flyctl versions. Update flyctl first. If `fly tokens create` fails, check the [community forum](https://community.fly.io/) for known issues with your version. *** ## Regional issues Fly runs on bare metal in [17 regions](/reference/regions). Individual hosts or regions can have issues independent of the rest. **Check status first:** [status.flyio.net](https://status.flyio.net) If your app is down in one region but the status page is clear, the issue might be specific to your host. Run: ``` fly status ``` Check which region your Machines are in and whether they're healthy. **Mitigation: deploy to multiple regions.** If all your Machines are in `iad` and `iad` has problems, your app is down. Spread across regions: ``` fly scale count 2 --region iad,ord ``` For databases, keep read replicas in a second region. For apps where latency matters, pick regions close to your users — `lhr` and `ams` for Western Europe, `nrt` and `sin` for Asia-Pacific. If a region is down and you need to deploy urgently, scale into a healthy region: ``` fly scale count 1 --region ord ``` *** ## Related topics * [App configuration reference](/reference/configuration) * [Fly Machines](/machines) * [Networking on Fly.io](/networking) * [Autostart and autostop](/launch/autostop-autostart) * [Fly.io status page](https://status.flyio.net) # Guides and Examples Source: https://docs.fly.io/guides A short index of Fly.io guides: the going-to-production checklist, running cron jobs with Supercronic, and running an SSH server on a Fly Machine. A collection of guides and examples for working with Fly.io. * [Going to production](/apps/going-to-production/) * [Crontab with Supercronic](/blueprints/supercronic/) * [Run an ssh server](/blueprints/opensshd/) # Our Levels Source: https://docs.fly.io/hiring/company-levels Fly.io's four seniority levels, L1 through L4 (staff): the scope each owns, why pay is fixed per level with no negotiation, and how leveling up works. We break out seniority into four levels, L1 to L4 (staff). * **L1's** tend to be early-career or people trying out a new specialty. L1's get more management support than folks at other levels. L1's focus on continuously absorbing new information about our users and how to be effective at Fly.io, and aim to be increasingly autonomous as they gain more experience here. * **L2's** will generally be responsible for running a project start-to-finish. An L2 is mostly self-directed about implementation, works with L3s on the plan, and collaborates with their manager and other teammates on their project's direction. An L2 has personal responsibility for the "how" of what they're working on, but shares responsibility for the "what" and "why". An L2 makes consistent progress on their work by continuously minimizing scope, incorporating feedback, trying different approaches and solutions, and deciding what will deliver the most value for users. * **L3's** have an even-bigger scope. We delegate to L3s entire functional areas and trust them to make smart decisions, not just about how we build things, but about what things to build. L3s are tuned in to our customers and independent sources of authority about their corner of Fly.io. L3s might be hands-on with the day-to-day work of the team, but they're also a resource for their teammates: they're helping L2s figure out what the next best thing to focus on is, and helping L1s figure out the right sequence of work to get a project done. * **L4's** are our staff level. An L4 is a leader across multiple different parts of our platform, product, or function. They influence work on lots of teams, not just one. They're independently responsible for making big new things happen; an L3 leads a team, an L4 might create new teams. In order to optimize for pay equity, Fly.io doesn't negotiate salaries. Instead, we have a standardized salary for each of these levels. You can go from L1 to L2, or from L2 to L3, etc. In fact, the most important job that managers at Fly.io have is leveling people up. [We prefer small teams and bottom-up product decisions](/hiring/working#we-prefer-small-teams-and-bottom-up-decisions), which incentivizes us to get people as autonomous as we can. Going down a level is more problematic, and as a rule of thumb we don't do it. So we're particularly careful about leveling people in the door. # Our Hiring Process Source: https://docs.fly.io/hiring/hiring No interviews, no resumes: Fly.io hires engineers through untimed at-home work-sample challenges and a typed Slack chat. Covers start dates and pay. **No interviews. No resumes.** The standard tech industry hiring process doesn't work, and we don't use it. Instead, we're weird about hiring. We've been delivering these engineering roles, some of them for years, and we have a good idea of what the work entails. We've taken the time to distill that work into challenge problems. We hire by giving you challenges to do at home, and then grading the results. > [!NOTE] > > Your resume is not part of our decision-making process. Feel free to send us a resume! We'll look at it! But we'll forget about it when we grade your challenge results. > > People ask us all the time if they should apply given they don't have much job experience in the work we do. Well: for any of our roles, we'll tell you what the hiring challenges are. If you can knock them out, go for it. We love discovering talent, and, just as importantly, we're set up to actually do it. We are serious about this process, and have built it carefully. For candidates with experience (or aptitude) delivering this kind of work, we believe our whole process will take less time than a standard tech industry interview gauntlet. 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 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. So that's another win: if you find our challenges dreadful, you've learned something important about whether you're going to enjoy the actual work here. ## Our Process In More Detail 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. 2. We say "hi", lay out our hiring challenges, and give you some time to ask us questions. We're happy to field questions! 3. 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. 4. 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. 5. That's it! If everything goes well, the next step is us working out an offer with you. ## What About Time Limits? We don't take delivery time into account when grading challenges. Take a day, take a week, take a month. 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 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 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 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. 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. 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. ## 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. 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 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. # Join Our Team Source: https://docs.fly.io/hiring/index Work at Fly.io: the Rust, Go, Ruby, and Elixir stack, open engineering roles, challenge-based hiring, and a remote-first culture with equal pay worldwide. ![Illustration by Annie Ruygt of a bird working on a laptopn](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/engineering-jobs.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=52ed67682372cfceb9ebef3197d06d6a) **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 they are around the world. We have fun problems coming out of our ears and we're happy to share them. Let's get you up to speed on what we're like and how we hire. ## Our Stack **The tl;dr is that we build on Rust, Go, Ruby, and Elixir, on Linux.** If you're comfortable with any of those, we probably have interesting roles for you. [Learn more about our stack here](/hiring/stack). ## Our Roles We're generally hiring all across Fly.io. We've got roles on our API backend, defining our developer experience; on our Elixir frontend; in security engineering; on infrastructure; and, of course, on the platform itself. [Learn more about our roles here](/hiring/roles). ## Our Hiring Process We're weird about hiring! We're always happy to talk, but we're skeptical of resumes and interviews. We respect career experience but we aren't hypnotized by it, and we're thrilled at the prospect of discovering new talent. We hire with work-sample challenges, in a process calibrated to take less time than a standard tech industry interview hazing. [Learn more about our hiring process here](/hiring/hiring). ## Working At Fly.io Fly.io is remote-first (no central office!) and hires around the world, with the same USA-based comp schedule everywhere in the world. [Here's more than you want to know about what it's like to work here](/hiring/working). # Our Engineering Levels Source: https://docs.fly.io/hiring/levels Fly.io's engineering ladder: L1s get broad projects, L2s run features end-to-end, L3s lead teams, and L4 staff engineers shape many parts of the platform. We break out engineering seniority into four levels, L1 to L4 (staff). * **L1 Engineers** tend to be early-career or people trying out a new specialty. L1's get more management support than other engineers. If you're an L2 and things aren't going well, that might be on you. If you're an L1, it's probably on us. L1's get a wider variety of projects; we want them getting as much contact with our stack as possible. * **L2 Engineers** are our middle-tier. An L2 will generally be responsible for running down a new feature, end-to-end. An L2 is mostly self-directed about implementation, works with L3s on designs, and collaborates on product direction. An L2 has personal responsibility for the "how" of what we're building, but shares responsibility for the "what" and "why". A successful L2 can fill any given week with work that will improve the lives of our users, but might need help to fill a dozen consecutive weeks. * **L3 Engineers** lead teams. We delegate to L3s entire functional areas of our platform and trust them to make smart decisions, not just about how we build things, but about what things to build. L3s are tuned in to our customers and independent sources of authority about their corner of Fly.io. L3s code, but they're also a resource for their team: they're helping L2s figure out what the next best feature to build is, and helping L1s figure out the right sequence PRs to get a feature done. * **L4 Engineers** are our staff level. An L4 is a technology leader across multiple different parts of our platform. They influence work on lots of teams, not just one. They're independently responsible for making big new things happen; an L3 leads a team, an L4 might create new teams. In order to optimize for pay equity, Fly.io doesn't negotiate salaries. Instead, we have a standardized salary for each of these levels. You can go from L1 to L2, or from L2 to L3. In fact, the most important job that engineering management at Fly.io has is leveling people up. [We prefer small teams and bottom-up product decisions](/hiring/working#we-prefer-small-teams-and-bottom-up-decisions), which incentivizes us to get people as autonomous as we can. Going down a level is more problematic, and as a rule of thumb we don't do it. So we're particularly careful about leveling people in the door. # How To Ask Us Questions Source: https://docs.fly.io/hiring/questions Questions count in Fly.io hiring. Examples of yes/no, decision-offloading, and spec-assuming questions, and how to rewrite each to get useful answers. If you're observant, you'll notice we gave this its own section in our hiring documentation. Asking us questions is a big deal. We are paying attention to the questions you ask us. We have a lot to say about questions. But first, two crucial things to know about questions and our hiring process: 1. You can't ask us too many questions. 2. If you have to ask whether to ask a question, ask it. We're not going to ding you for asking us an imperfect question. That said, there are effective ways to ask us questions, and there are ineffective ways. If you're struggling with our process, it might help to change the approach you're taking. Here are several classes of questions that are unlikely to help you much, and how to write them more effectively: ### "Yes/No" questions, and questions with only one possible answer. *Example: "Is a region a required field on a Machine `create`?".* You'll usually get the answer you were looking for with a question like this. But you won't get anything else. You haven't left space for people on our team to contribute, or to tell you things you haven't thought about. Here's a better version of that question: "What happens if a user tries to `create` a Machine without specifying region?" ### Questions that offload decision-making. *Example: "What do you think about `creating` a new Machine when a user pushes a button?"* We want you to make decisions. We won't ding you for asking questions like this, but our answers won't be helpful. Look for words like "should", or "too slow", or "acceptable", or "suitable" in your question. Sometimes they're OK, and sometimes they're a sign that you're asking us to do your thinking for you. There isn't a better version of this question, but there's a better approach: ask questions that probe us about how things work here, so you can arm yourself with enough details to make these decisions yourself. ### Questions that assume there's a spec. *Example: "What do users want from the Machine `create` button?"* We don't have product managers. You won't get a spec, because there isn't one. Customers are unreliable narrators and can't usually verbalize their needs. We hire team members who speak the same developer language as our customers, and rely on them to engage with those customers directly. That's a thing we're evaluating you on. Here's a better version of that question: "How often does the Machine `create` function get called in a day?" # Our Roles Source: https://docs.fly.io/hiring/roles Engineering roles at Fly.io: Rails backend, Elixir UX, security, infrastructure, and Rust and Go platform work on fly-proxy, flyd, and private networks. Currently, our engineering roles include: * Backend development, in Rails (and some Go, and some Elixir). Backend makes our API work, and defines much of the developer experience of the product. Rails and Postgres are the big things here. * UX development, in Elixir (and some Rails). UX is user interface, and, importantly, new customer experience and growth development. If it has a web interface, this is the role where it happens. * Security engineering, in Rust, Go, Ruby, and whatever else makes sense. A modern startup security practice includes corpsec (protecting our staff and their machines), appsec (hunting and killing security vulnerabilities in our code), and infrasec (hardening the operating systems we run that code on). Currently, security engineers in our practice do all three of those roles. * Infrastructure engineering, in Go, Ruby, Rust, and whatever else makes sense. Infra keeps the spice flowing. Infra opens up new regions, grows regions to meet capacity, and outguesses our tricksy software and operating systems with monitoring tools that predict and head off outages. * Platform development, in Rust and Go. Platform runs the apps, and connects them to the Internet. It's the "engine" of Fly.io. Platform development is a big category. We can further divide it — bear in mind that people switch teams within platform, so these can be fluid: * Proxy: this is overwhelmingly Rust-based work on `fly-proxy`, building features that connect customer apps to the Internet. * Machines: this is mostly Go-based work on `flyd` and other orchestration components (think: building a better, simpler K8s, at global scale). Exception: `init` is part of this work, and it's Rust. * Private Networks: this is Go, Rust, and BPF work extending and streamlining our WireGuard-backend private networks. # Our Stack Source: https://docs.fly.io/hiring/stack What Fly.io runs on: fly-proxy in Rust, flyd and flyctl in Go, Firecracker VMs with a Rust init, corrosion, a Rails GraphQL API, and Phoenix LiveView. Our platform, the stuff that actually runs apps for our customers, is built on Rust and Go. Developers on all of our platform teams switch-hit between those two languages. We love them both, and that's an attribute we're looking for in platform devs. When a user aims their browser at an app hosted on Fly.io, they're talking to `fly-proxy`, a Rust / Tokio / Hyper proxy that powers our Anycast network. `fly-proxy` uses `attache`, our Go SQLite Consul cache, and `corrosion`, our Rust SWIM-based state replication service, to route requests to the right worker and VM. Those VMs were booted up by `flyd`, our Go orchestrator. Persistent encrypted storage for the VMs was provisioned by `vold`, another orchestration component. `flyd` leverages the Go-based `containerd` ecosystem to convert Docker containers into root filesystems for our VMs. The VMs themselves run under `firecracker`, Amazon's Rust-based hypervisor (and the engine behind Lambda and Fargate). Our hardware! Amazon's hypervisor code. Inside the VM, our code gets control again: we own the `init` that spins things up in the VM. `init` is Rust. More and more of our fun VM tricks are features of our `init`. To tell us what containers to boot up, our customers talk to our GraphQL API. The backend for that API is a Rails app, running on Postgres. Most of our developer UX happens in `flyctl`, our Go-based CLI. We're a CLI-first company. Fancy customer UI stuff? Elixir! We love Elixir and sponsor the Phoenix project. Generally, new UI stuff at Fly.io happens in Elixir/Phoenix/LiveView. LiveView screams on Fly.io. # Working at Fly.io Source: https://docs.fly.io/hiring/working Life at Fly.io: remote-first with the same comp worldwide, limited visa sponsorship, small teams, a shared on-call rotation, and a no-refactoring rule. ## Fly.io Is A Remote-First Company There are a couple Fly.io people in Chicago. Apart from that, Fly.io people work all over the world. We've been remote since the company started, and we're careful to maintain a remote-first culture. Like most companies, we do most of our daily work on a Slack. Slack isn't great for asynchronous communications and is especially rough for people in different time zones, so we also run an internal message board. About half of our work (and more of our decision-making) gets done on the message board. We don't do much email. We're not big on meetings. ## 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 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. ## Fly.io Hires Around The World We have team members in Europe, Rwanda, South Africa, Mexico, Argentina, Canada, and, of course, the USA. With few exceptions we're open to hiring people wherever they are (some teams may require some working-hours overlap; feel free to ask, and we'll be sure to tell you). **Fly.io's comp package is the same everywhere in the world.** We don't adjust comp for cost-of-living. Our comp packages are competitive for the US market. ## Visa Sponsorship 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. 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. ## We Prefer Small Teams And Bottom-Up Decisions We like single-pizza teams: 3-4 people is a happy number for us. We're a product for developers, by developers. We have a [thriving community](https://community.fly.io) and everyone is expected to keep up with it and develop a sense for what's worth doing and what needs fixing. We don't have formal product roadmaps or schedules; we ship stuff when it's ready. ## Leveling Levels at Fly.io are relatively informal. We're pretty flat, as companies go. Small teams, like we just said. Our current [leveling](/hiring/company-levels) looks 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're Ruthless About Doing Customer-Visible Work We want our teams to set their own direction as much as possible, but we'll shoot things down if they're not going to move the dials for our users. We're vigilant about tar pits and projects that spin our wheels and will do our best to keep you out of them. We're not big believers in "tech debt". We have a 3-month "no refactoring" rule for new hires. This isn't everyone's preferred work style! We try to be up front about stuff. ## Everyone Shares An On-Call Rotation Fly.io is 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 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. ## Team Flexibility We get asked this a lot. Moving from a "big" category, like from Elixir UI to Rust platform work, will take some conversation and planning. But within a category, especially for platform work, you'll get opportunities to work on multiple things. ## Open Source Work We'd open source the whole platform if we could (it'd take a year just to beat some of these components into releasable shape). We're thrilled at opportunities to open source stuff. If something you're working on can be open, we'll work with you to open it up. We don't have a lot of secrets. # JS Framework Guides Source: https://docs.fly.io/js/frameworks Deploy guides for JavaScript frameworks on Fly.io: Astro, Deno, Meteor, Next.js, Nuxt, RedwoodJS, Remix and SvelteKit, each building a simple app. ![Illustration by Annie Ruygt of two 3-eyed humanoids relaxing with a drink](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/js-lander.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=4a6359381ddbdac35ddd678a1d7e7567) Each one of the language guides will take you through creating and deploying a simple application. * [Deploy an Astro App](/js/frameworks/astro/) * [Deploy a Deno App](/js/frameworks/deno/) * [Deploy a Meteor App](/js/frameworks/meteor/) * [Deploy a Next.js App](/js/frameworks/nextjs/) * [Deploy a Nuxt App](/js/frameworks/nuxtjs/) * [Deploy a RedwoodJS App](/js/frameworks/redwood/) * [Deploy a Remix App](/js/frameworks/remix/) * [Deploy a SvelteKit App](/js/frameworks/svelte/) If you don't see a framework supported here that you'd like to run on Fly.io, [tell us in the Community](https://community.fly.io/). ## Deploy an Astro App Astro is the web framework for content-driven websites. ## Deploy a Deno App Deno is the open-source JavaScript runtime for the modern web. ## Deploy a Meteor App Meteor is a full-stack JavaScript framework for building modern web and mobile applications. ## Deploy a Next.js App NextJS is the React Framework for the Web. ## Deploy a Nuxt App Nuxt is the intuitive Vue Framework. ## Deploy a RedwoodJS App Redwood is the full-stack web framework designed to help you grow from side project to startup. ## Deploy a Remix App Remix is a full stack web framework that lets you focus on the user interface and work back through web standards to deliver a fast, slick, and resilient user experience. ## Deploy a SvelteKit App SvelteKit is a framework for rapidly developing robust, performant web applications using Svelte # Deploy an Astro App Source: https://docs.fly.io/js/frameworks/astro Deploy an Astro app to Fly.io, static or SSR. fly launch detects Astro, generates a Dockerfile and fly.toml, and deploys it in a few commands. In this guide we’ll learn how to deploy an [Astro](https://astro.build/) application on Fly.io. You can deploy your [Astro](https://astro.build/) app on Fly.io with minimal effort, our CLI will do the heavy lifting. You can use your existing Astro app or you can create one from scratch. Astro applications can be served as either **static pages** *or* with **server-side rendering (SSR)**. On this page you'll find guides to deploying both varieties. ## *Deploy a static Astro site* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. Next, if you don't have an existing Astro application, you can create one with the following command: ```bash theme={null} npm create astro@latest ``` With the project setup out of the way, you're ready to start deploying! If you don't have a Dockerfile yet, `fly launch` will generate one for you, as well as prepare a `fly.toml` file. ```bash cmd theme={null} cd my-astro-app fly launch ``` ```output output theme={null} Scanning source code Detected an Astro app Creating app in [redacted]/my-astro-app We're about to launch your NodeJS app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: my-astro-app (derived from your directory name) Region: Seattle, Washington (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? (y/N) No Created app 'my-astro-app' in organization 'personal' Admin URL: https://fly.io/apps/my-astro-app Hostname: my-astro-app.fly.dev installing: npm install @flydotio/dockerfile@latest --save-dev ... found 0 vulnerabilities create Dockerfile Wrote config file fly.toml Validating [redacted]/my-astro-app/fly.toml Platform: machines ✓ Configuration is valid ==> Building image ... --> Building image done ==> Pushing image to fly ... --> Pushing image done ... Watch your deployment at https://fly.io/apps/my-astro-app/monitoring Provisioning ips for my-astro-app Dedicated ipv6: 2a09:6739:1::30:xxxx:0 Shared ipv4: 66.241.124.xxx Add a dedicated ipv4 with: fly ips allocate-v4 This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Checking DNS configuration for my-astro-app.fly.dev Visit your newly deployed app at https://my-astro-app.fly.dev/ ``` That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * [`fly logs`](/flyctl/cmd/fly_logs) - Tail your application logs * [`fly status`](/flyctl/cmd/fly_status) - View your app's current deployment status * [`fly ssh console`](/flyctl/cmd/fly_ssh_console) - Open a terminal on your VM * [`fly deploy`](/flyctl/cmd/fly_deploy) - Deploy the application after making changes ## *Deploy an Astro site with SSR* Astro supports server-side rendering (SSR) through the use of **adapters**. There are a handful of adapters that are officially maintained by the Astro team. When using the [`@astrojs/node`](https://docs.astro.build/en/guides/integrations-guide/node/) adapter, Fly.io will automatically detect this during `fly launch` and if no existing Dockerfile is found, it will generate a new one with the appropriate start command and environment variables. You can add the necessary Node adapter like so: ```bash theme={null} npx astro add node ``` This will install `@astrojs/node` and make the appropriate changes to your `astro.config.*` file in one step. ## Generating your Astro Dockerfile As discussed earlier, running `fly launch` will generate a Dockerfile for you if one does not already exist. Separately, you can also generate your Dockerfile using [Dockerfile generator](https://www.npmjs.com/package/@flydotio/dockerfile). Once installed, it can be run using `npx @flydotio/dockerfile` for Node applications or `bunx @flydotio/dockerfile` for Bun applications. You'll see it referenced throughout this article for various use cases. # Deploy a Deno App Source: https://docs.fly.io/js/frameworks/deno Deploy a Deno app to Fly.io. Package a Deno server in a Docker image, configure it with fly launch, and deploy it to a Fly Machine. In this guide we’ll learn how to deploy a [Deno](https://deno.land/) application on Fly.io. ## *The Hellodeno Application* Our example will be a basic "hello world" example using Deno and [Dinatra](https://github.com/syumai/dinatra). You can get the code for the example from [the hellodeno GitHub repository](https://github.com/fly-apps/hellodeno). Just `git clone https://github.com/fly-apps/hellodeno` to get a local copy. Here's all the code: ```typescript theme={null} import { app, get, post, redirect, contentType, } from "https://denopkg.com/syumai/dinatra/mod.ts"; const greeting = "

Hello From Deno on Fly!

"; app( get("/", () => greeting), get("/:id", ({ params }) => greeting + `
and hello to ${params.id}`), ); ``` We'll call this file `main.ts`. We also want a `deps.ts` file for dependencies, here it is: ```typescript theme={null} export { app, get, post, redirect, contentType, } from "https://denopkg.com/syumai/dinatra/mod.ts"; ``` There's nothing else to run. Deno will manage getting packages for itself. ## Running The Application Run `deno run --allow-net main.ts` to start the application: ```bash cmd theme={null} deno run --allow-net main.ts ``` ```output output theme={null} listening on http://0.0.0.0:8080/ ``` And connect to localhost:8080 to confirm that you have a working Deno application by receiving a greeting. Now to package it up for Fly.io. There are a number of ways to do this. You can use flyctl's own simple builder, ## Install Flyctl and Login We are ready to start working with Fly.io, and that means we need `flyctl`, our CLI app for managing apps on Fly.io. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly.io](/getting-started/sign-up-sign-in). ## Configuring the App for Fly.io Each Fly App needs a `fly.toml` file to tell the system how we'd like to deploy it. That file can be automatically generated with the command `fly launch` command. This command will also generate a Dockerfile for deployment. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Creating app in /fly/guides/hellodeno Scanning source code Detected Deno app ? Select organization: myorg (myorg) ? Select region: ams (Amsterdam, Netherlands) Created app empty-sea-2541 in organization myorg Wrote config file fly.toml Your app is ready. Deploy with `flyctl deploy` ? Would you like to deploy now? (y/N) ``` First you'll be prompted for an organization. Organizations are a way of sharing applications between Fly.io users. When you are asked to select an organization, there should be one with your account name; this is your personal organization. Select that. Then, pick the region you want to deploy in. ## Inside `fly.toml` The `fly.toml` file now contains a default configuration for deploying your app. While creating that file, `fly` has also created a Fly-side application slot with the same name, "hellodeno". If we look at the `fly.toml` configuration file we can see the name in there: ```toml theme={null} app = "empty-sea-2541" [processes] app = "run --allow-net ./example.ts" ... ``` We can also see a default `run` command for the main web process. We can change that to refer to `./main.ts`. The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name/value at the start. That name will be used to identify the application to the Fly.io platform. The rest of the file contains settings to be applied to the application when it deploys. ## Deploying to Fly.io We are now ready to deploy our containerized app to Fly.io. At the command line, just run: ```bash theme={null} fly deploy ``` This will get the app name `empty-sea-2541` from our `fly.toml` file. Then, `fly` will start deploying our application to Fly.io using the Dockerfile. `fly` will return you to the command line when it's done. ## Viewing the Deployed App If you want to find out more about the deployment. The command `fly status` will give you all the essential details. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = empty-sea-2541 Owner = dj Version = 0 Status = running Hostname = empty-sea-2541.fly.dev Allocations ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED 73f825ad 0 fra run running 1 total, 1 passing 0 6m34s ago ``` ## *Connecting to the App* The quickest way to view your application is to run `fly apps open` which will open your browser on the URL for your application. Run `fly apps open /name` to get an extra greeting from the app. If you want to manually enter a URL to check, remember to replace `empty-sea-2541.fly.dev` with the hostname you got from `fly status` and connect to `http://hellodeno.fly.dev/` where you should find a greeting - it will normally be upgraded to a secure connection. Use `https://hellodeno.fly.dev` to start with a secure connection. Add `/name` and you'll get an extra greeting from the hellodeno application. ## Bonus Points If you want to know what IP addresses the app is using, try `fly ips list`: ```bash cmd theme={null} fly ips list ``` ```out out theme={null} TYPE ADDRESS REGION CREATED AT v4 213.188.199.2 global 2021-04-22T23:23:01Z v6 2a09:8280:1:2958:3b6c:fdfd:70f9:da9d 2021-04-22T23:23:01Z ``` ## Arrived at Destination You have successfully built, deployed, and connected to your first Deno application on Fly.io. # Deploy a Meteor App Source: https://docs.fly.io/js/frameworks/meteor Deploy a Meteor app to Fly.io. fly launch detects Meteor, generates a Dockerfile and fly.toml, and deploys your app to a Fly Machine. In this guide we’ll learn how to deploy a [Meteor](https://meteor.com/) application on Fly.io. > [!NOTE] > > This guide is intended for users of Meteor 3. If you're using Meteor 2 and want to migrate, please see the [Meteor 3.0 migration guide](https://v3-migration-docs.meteor.com/). Meteor is a full-stack JavaScript framework for building modern web and mobile applications. You get real-time features for free, built-in accounts, TypeScript support, and a powerful CLI tool for creating and managing your app. We'll start off by installing Meteor: ```bash cmd theme={null} npx meteor ``` ```output output theme={null} => Arch: os.osx.arm64 => Meteor Release: 3.0.0 Downloading |█████████████████████████████████░░░░░░░| 82% ``` ## *Generate the Meteor app* We'll assume you have NodeJS installed already. Meteor 3 requires NodeJS v20 or higher. We'll be using a skeleton web application generated by Meteor. This is a bare-bones app that does not need a MongoDB database. Let's create the new project by running `meteor create`. We'll choose a "minimal" skeleton, which is a good choice for a new project and to get up and running quickly. ```bash cmd theme={null} meteor create hello-meteor --minimal --release 3.0.0 ``` ```output output theme={null} Created a new Meteor app in 'hello-meteor'. To run your new app: cd hello-meteor meteor If you are new to Meteor, try some of the learning resources here: https://www.meteor.com/tutorials When you’re ready to deploy and host your new Meteor application, check out Cloud: https://www.meteor.com/cloud ``` ## *Preview your Application* You can preview your production build locally, simply by executing the command `meteor`. ```bash cmd theme={null} meteor ``` ```output output theme={null} [[[[[ ~/hello-meteor ]]]]] => Started proxy. => Started HMR server. => Started your app. => App running at: http://localhost:3000/ ``` ## *Install flyctl and Login* We are ready to start working with Fly.io, and that means we need `flyctl`, our CLI app for managing apps on Fly.io. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly.io](/getting-started/sign-up-sign-in). ## *Deploy the app on Fly.io* Each Fly App needs a `fly.toml` file to tell the system how we'd like to deploy it. That file can be automatically generated with `fly launch`. This command will also generate a Dockerfile for deployment. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Meteor app Creating app in ~/Demo/hello-meteor We're about to launch your Meteor app on Fly.io. Here's what you're getting: Organization: Demo (fly launch defaults to the personal org) Name: hello-meteor (generated) Region: Johannesburg, South Africa (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: (not requested) Tigris: (not requested) ? Do you want to tweak these settings before proceeding? No Created app 'hello-meteor' in organization 'demo' Admin URL: https://fly.io/apps/hello-meteor Hostname: hello-meteor.fly.dev installing: npm install @flydotio/dockerfile@latest --save-dev added 33 packages, and audited 143 packages in 4s 32 packages are looking for funding run `npm fund` for details found 0 vulnerabilities create Dockerfile Wrote config file fly.toml Validating ~/Demo/hello-meteor/fly.toml ✓ Configuration is valid ==> Building image Remote builder fly-builder-frosty-night-3947 ready ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/hello-meteor/monitoring Provisioning ips for hello-meteor Dedicated ipv6: 2a09:8280:1::3c:c2d4:0 Shared ipv4: 66.241.124.224 Add a dedicated ipv4 with: fly ips allocate-v4 This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Checking DNS configuration for hello-meteor.fly.dev Visit your newly deployed app at https://hello-meteor.fly.dev/ ... ``` ## *Inside `fly.toml`* The `fly.toml` file now contains a default configuration for deploying your app. In the process of creating that file, `flyctl` has also created a Fly.io application slot of the same name, "hello-meteor". If we look at the `fly.toml` configuration file we can see the name in there: ```toml theme={null} app = "hello-meteor" primary_region = "jnb" [env] PORT = "3000" ROOT_URL = "https://hello-meteor.fly.dev/" [http_service] internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ["app"] ... ``` We make sure to set the `ROOT_URL` and `PORT` environment variables. These are important to Meteor. When you eventually add a [custom domain](/networking/custom-domain) to your app, you'll want to make sure your `ROOT_URL` is set to the domain name. The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name/value at the start. That name will be used to identify the application to the Fly.io platform. The rest of the file contains settings to be applied to the application when it deploys. ## *Viewing the Deployed App* If you want to find out more about the deployment. The command `fly status` will give you all the essential details. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = hello-meteor Owner = personal Hostname = hello-meteor.fly.dev Image = hello-meteor:deployment-01J2S3S8A9TDR1D2AYZSYJWTHH Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 3287154db00658 1 jnb started 2024-07-14T17:26:16Z app 4d890d17a6d768 1 jnb stopped 2024-07-14T17:23:03Z ``` ## *Connecting to the App* The quickest way to browse your newly deployed application is with the `fly apps open` command. ```bash cmd theme={null} fly apps open ``` ```output output theme={null} Opening https://hello-meteor.fly.dev/ ``` Your browser will be sent to the displayed URL. ## *Session Affinity (aka 'Sticky Sessions')* Sticky sessions are a feature of a load balancer that ensures that all requests from a client are routed to the same server. This can be important for Meteor applications depending on the client/server architecture of your app, as it ensures that the client is always connected to the same server, and that any changes made to the server are reflected in the client. The easiest way to achieve this on Fly.io is to run your app with a maximum of one Machine per region. The [Fly Proxy](/reference/fly-proxy) will take care of load balancing clients — sending requests to the Machine closest to them. A better option, is to use [dynamic request routing](/networking/dynamic-request-routing) with the `fly-replay` response header. You'll need to implement some additional code (roughly 18 lines) that uses the `meteor/webapp` package and plugs into your app's server-side entry point. This example uses middleware to create a client-side cooking containing the Machine ID to "pin" requests to. The process is explained in greater detail [here](/blueprints/sticky-sessions). First, install `cookie-parser`: ```bash theme={null} npm install cookie-parser ``` Now create a new file called `sticky.js` wherever you keep your server server-side code: ```js theme={null} import { WebApp } from 'meteor/webapp'; import cookieParser from "cookie-parser"; WebApp.connectHandlers.use(cookieParser()); WebApp.connectHandlers.use('/',(request, response, next) => { if (!process.env.FLY_MACHINE_ID) { next(); } else if (!request.cookies["fly-machine-id"]) { const maxAge = 24 * 60 * 60 * 1000; // 24 hours response.cookie("fly-machine-id", process.env.FLY_MACHINE_ID, { maxAge }); 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(); } }); ``` Make sure to import that module when initializing your Meteor server, then you should see clients routed to the same Fly Machine for the duration of the session. ## *Bonus Points* Let's take a look at a slightly more advanced app that uses a MongoDB database for storing account details and user data — but also for real-time UI updates. First, we'll need a MongoDB database to connect to. If you have one, great! If not, it's easy to deploy a development MongoDB database on Fly.io. If you're deploying a production app, we recommend that you look at a managed service like [MongoDB Atlas](https://www.mongodb.com/atlas). Save the below as your `mongo.fly.toml`, then run `fly launch -c mongo.fly.toml --no-deploy` to create the database app without deploying it. ```toml theme={null} app = "hello-mongo" primary_region = "jnb" [build] image = 'mongodb/mongodb-community-server' [experimental] cmd = ["mongod", "--bind_ip_all", "--ipv6"] [[services]] internal_port = 27017 protocol = "tcp" [[services.ports]] handlers = ["tls"] port = 27017 force_https = false [[vm]] memory = '1gb' cpu_kind = 'shared' cpus = 1 [[mounts]] source = "mongo_data" destination = "/data/db" ``` Now, set the default username and password for the database: ```bash theme={null} fly secrets set MONGODB_INITDB_ROOT_USERNAME=admin MONGODB_INITDB_ROOT_PASSWORD=password ``` And finally, deploy it: ```bash theme={null} fly deploy --no-public-ips ``` Ok, now we can deploy our Meteor app. We'll use the Simple Tasks example app: ```bash theme={null} fly launch --from https://github.com/fredmaiaarantes/simpletasks --no-deploy ``` This simultaneously clones the GitHub repo and launches the new app on Fly.io Set the `MONGO_URL` secret so Meteor knows where to connect to: ```bash theme={null} fly secrets set MONGO_URL=mongodb://admin:password@hello-mongo.internal:27017/meteor ``` And deploy the app: ```bash theme={null} fly deploy ``` And finally, open the app. It'll take a second longer on the first run while database migrations are performed: ```bash theme={null} fly apps open ``` That's it! You should see the app running: ![A screenshot of the Simple Tasks Meteor app login page.](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/simple-tasks.webp?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=c5faa17649ed9d9380b607f36147ae4c) ## *Arrived at Destination* You have successfully built, deployed, and connected to your first Meteor application on Fly.io. # Deploy a Next.js App Source: https://docs.fly.io/js/frameworks/nextjs Deploy a Next.js app to Fly.io. fly launch detects Next.js and generates a Dockerfile and fly.toml, with standalone builds for smaller images. In this guide we’ll learn how to deploy a [Next.js](https://nextjs.org/) application on Fly.io. You can deploy your [Next.js](https://nextjs.org/) app on Fly with minimal effort, our CLI will do the heavy lifting. We'll be using a standard app generated with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app) CLI tool provided by Next.js. ## *Generate the Next.js app* We'll assume you have NodeJS installed already and can run `npx`. Refer to the [documentation](https://nextjs.org/docs/pages/api-reference/create-next-app#interactive) if you would like to use `yarn`, `pnpm` or `bun`. Now let's create a new project by running the `create-next-app`. This will scaffold a new project asking you if you'd like to set up some basic tooling such as TypeScript. ```bash cmd theme={null} npx create-next-app@latest hello-nextjs ``` ```output output theme={null} ✔ Would you like to use TypeScript? … No / Yes ... Initializing project with template: app-tw Installing dependencies: - react ... Installing devDependencies: - typescript ... Success! Created hello-nextjs at /.../hello-nextjs ``` Great! You should be able to run our app locally with `cd hello-nextjs && npm run dev` and access it at [http://localhost:3000](http://localhost:3000). ## *Deploy to Fly.io* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. Now let's launch your Next.js app from the root of your application. ```bash cmd theme={null} cd hello-nextjs fly launch ``` ```output output theme={null} Scanning source code Detected a Next.js app Creating app in /Users/mentels/work/hello-nextjs We're about to launch your Next.js app on Fly.io. Here's what you're getting: Organization: mentels (fly launch defaults to the personal org) Name: hello-nextjs (derived from your directory name) Region: Warsaw, Poland (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: (not requested) Sentry: false (not requested) ? Do you want to tweak these settings before proceeding? No Created app 'hello-nextjs' in organization 'personal' Admin URL: https://fly.io/apps/hello-nextjs Hostname: hello-nextjs.fly.dev ... Visit your newly deployed app at https://hello-nextjs.fly.dev/ ``` That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * [`fly logs`](/flyctl/cmd/fly_logs) - Tail your application logs * [`fly status`](/flyctl/cmd/fly_status) - View your app's current deployment status * [`fly ssh console`](/flyctl/cmd/fly_ssh_console) - Open a terminal on your VM * [`fly deploy`](/flyctl/cmd/fly_deploy) - Deploy the application after making changes In the process we've generated a [`fly.toml`](/reference/configuration) and `Dockerfile` in the root of the project. Change them if you need to customize your deployment. *** The following sections cover further steps you can apply for your Next.js app deployment. ## Standalone builds (recommended) To reduce the size of the final image deployed to Fly.io, **we recommend using the [standalone output](https://nextjs.org/docs/app/api-reference/next-config-js/output#automatically-copying-traced-files)** in your `next.config.js` (or `next.config.mjs`) file: ```javascript theme={null} /** @type {import('next').NextConfig} */ const nextConfig = { output: "standalone" }; export default nextConfig; ``` When running `fly launch` or using the [Dockerfile generator](https://www.npmjs.com/package/@flydotio/dockerfile), if this `"standalone"` value is set in your Next config, your Dockerfile will be updated such that only the files necessary for a production environment will be used in the final image. Using the standalone setting and updating your Dockerfile can reduce your total image size by \~400mb, so we highly recommend this approach! You can also manually update your Dockerfile to accommodate a standalone Next.js app. After running the build script (`npm run build`), there should typically be a final `COPY` instruction towards the end of one's Dockerfile. Replace that with the following: ```dockerfile theme={null} COPY --from=build /app/.next/standalone /app COPY --from=build /app/.next/static /app/.next/static COPY --from=build /app/public /app/public ``` Lastly, update your `CMD` to the following: ```dockerfile theme={null} CMD [ "node", "server.js" ] ``` ## Generating your Next.js Dockerfile While you're welcome to write your own Dockerfile, the easiest way to get started with this is to use the [Dockerfile generator](https://www.npmjs.com/package/@flydotio/dockerfile). Once installed, it can be run using `npx @flydotio/dockerfile` for Node applications or `bunx @flydotio/dockerfile` for Bun applications. You'll see it referenced throughout this article for various use cases. ## Connecting to databases Unlike some other frameworks, Next.js does not bundle a database adapter. Instead, you are free to choose the ORM or node module you wish. Fly.io will provide a `DATABASE_URL` that you can use at runtime to connect to your database. How you will use this will depend on the database module you choose. [Prisma](https://www.prisma.io/nextjs) is a popular choice, and connecting to your database using Prisma is done through your `prisma/schema.prisma` file. An example: ``` datasource db { provider = "postgresql" url = env("DATABASE_URL") } ``` If you are unsure how to connect using your favorite adapter, check out our [Vanilla with Candy Sprinkles](https://fly.io/blog/vanilla-candy-sprinkles/) blog entry and select the configuration that most closely matches your application. If you still have questions, post on our [community forum](https://community.fly.io/). ### Build time secrets This approach allows you to inject secrets (such as database URLs) that are only available at build time. > [!NOTE] > > **This approach will not work for SQLite3,** as the build Machine still won’t have access to your volume. However, it can be used with PostgreSQL, MySQL and other such databases. First, you need to obtain the secrets you will need to deploy. Often this is only the value of `DATABASE_URL`. If you don't know the value of this secret, `fly console` and `printenv DATABASE_URL` can obtain this value for you. Next, you need to modify your Dockerfile to mount a secret. The easiest way to do this is using the [Dockerfile generator](https://www.npmjs.com/package/@flydotio/dockerfile), like so: ```bash theme={null} npx @flydotio/dockerfile --mount-secret=DATABASE_URL ``` It is possible to mount multiple secrets, and you can read more about [mounting secrets](/apps/build-secrets#mounting-secrets) for further details. Finally, you need to pass the secret on each deploy: ```bash theme={null} fly deploy --build-secret DATABASE_URL=value ``` Replace *value* above with the actual secret. It might be worth putting this command into a shell script or batch file. ### Deferring static site generation An alternate approach, one that works with SQLite3 too, is to defer the running of the build step to just after deployment before your web server is started. The upside is that your build has full access to all of your deployment secrets and environment variables. This involves replacing the entry point in your Dockerfile (typically your `CMD`) with a script and having that script run `npm build` (or equivalent) prior to starting your server. You can let the [Dockerfile generator](https://www.npmjs.com/package/@flydotio/dockerfile) take care of these changes for you: ``` npx @flydotio/dockerfile --build=defer ``` Downsides of this approach: * Your deployment Machines will need enough memory to run a build. See: [`fly scale memory`](/flyctl/cmd/fly_scale_memory) and [`swap_size_mb`](/reference/configuration#swap_size_mb-option) for two options. * You may need to adjust the [grace\_period](/reference/configuration#http_service-checks) for any HTTP service checks. * If you are only running one Machine there will be a period of time where your server is inaccessible while the site is being statically generated. * If you run multiple Machines, the static site generation will be run on each increasing the total time before any changes are fully deployed. ## Exposing environment variables to the browser If you're a Next.js user you might know that it supports [exposing environment variables to the browser](https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser) using variables with names starting with `NEXT_PUBLIC_`. **These variables are fixed at build time**, and unlike runtime environment variables (which are only available on the server), they can't be changed unless the application is built again. In order to make these `NEXT_PUBLIC_` variables available, you can add them as `ARG` variables in your Dockerfile, as seen in the example below. **This should come after any actual build steps** (if you've used the Dockerfile generator or the Dockerfile created from `fly launch`, this would be after the line `FROM base as build`). ```dockerfile theme={null} # Build arguments ARG NEXT_PUBLIC_EXAMPLE="value" ARG NEXT_PUBLIC_OTHER="Other value" ``` Alternatively, if you'd prefer to use the Dockerfile generator, you can do so by running the following: ```bash theme={null} npx @flydotio/dockerfile "--arg-build=NEXT_PUBLIC_EXAMPLE:value" \ "--arg-build=NEXT_PUBLIC_OTHER=Other value" ``` ## Disabling telemetry in production During server startup, the following messages may appear in your log: ``` Attention: Next.js now collects completely anonymous telemetry regarding usage. This information is used to shape Next.js' roadmap and prioritize features. You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: https://nextjs.org/telemetry ``` The following command can be used to modify your Dockerfile to disable telemetry: ```bash theme={null} npx @flydotio/dockerfile --env-base=NEXT_TELEMETRY_DISABLED:1 ``` ## *Common pitfalls* ### Out of memory: Killed process If you should happen to see lines like the following in your logs: ``` Out of memory: Killed process ``` Two links that may be of help: * [fly scale memory](/flyctl/cmd/fly_scale_memory) * [swap\_size\_mb](/reference/configuration#swap_size_mb-option) ### Fetch failure when optimizing images If your application uses the `` element and your images aren't showing and you are seeing the following in your logs: ``` TypeError: fetch failed ``` Scan backward in your logs for the following: ``` Warning: For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended. Run 'npm i sharp', and Next.js will use it automatically for Image Optimization. Read more: https://nextjs.org/docs/messages/sharp-missing-in-production ``` Follow the instructions in the message, and then redeploy: ```bash theme={null} npm i sharp fly deploy ``` ### Cannot find module Your app doesn't start and you see an error like the following in your logs: ``` `Cannot find module 'typescript'` ``` Verify that the missing module appears in `package.json`'s `dependencies`, not just `devDependencies`. If you're using a Dockerfile generated by `dockerfile-node`, your image will be optimized for production and omit development dependencies by default. If the module is missing, add it to `dependencies` and re-deploy. # Deploy a Nuxt App Source: https://docs.fly.io/js/frameworks/nuxtjs Deploy a Nuxt app to Fly.io. fly launch generates a Dockerfile and fly.toml for your Nuxt project and deploys it to a Fly Machine. In this guide we’ll learn how to deploy a [Nuxt](https://nuxt.com) application on Fly.io. You can deploy your [Nuxt](https://nuxt.com) app on Fly with minimal effort, our CLI will do the heavy lifting. ## *Deploy a Nuxt app* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. If you don't already have a Nuxt app, you can create one: ```bash theme={null} npx nuxi@latest init hello-nuxt ``` Now let's launch your Nuxt app. ```bash cmd theme={null} cd <%= app_name %> fly launch ``` ```output output theme={null} Creating app in /Users/me/<%= app_name %> Scanning source code Detected a <%= detected %> app ? App Name (leave blank to use an auto-generated name): <%= app_name %> ? Select organization: flyio (flyio) ? Select region: mad (Madrid, Spain) Created app <%= app_name %> in organization soupedup ? Would you like to deploy now? Yes ==> Validating app configuration --> Validating app configuration done Services TCP 80/443 ⇢ 8080 Remote builder fly-builder-little-glitter-8329 ready ... 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 2 passing] --> v0 deployed successfully ``` That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * [`fly logs`](/flyctl/cmd/fly_logs) - Tail your application logs * [`fly status`](/flyctl/cmd/fly_status) - View your app's current deployment status * [`fly ssh console`](/flyctl/cmd/fly_ssh_console) - Open a terminal on your VM * [`fly deploy`](/flyctl/cmd/fly_deploy) - Deploy the application after making changes # Deploy a RedwoodJS App Source: https://docs.fly.io/js/frameworks/redwood Deploy a RedwoodJS app to Fly.io. fly launch generates a Dockerfile and fly.toml for your Redwood project and deploys it to a Fly Machine. In this guide we’ll learn how to deploy a [RedwoodJS](https://redwoodjs.com) application on Fly.io. We'll be using the standard web application generated by [RedwoodJS](https://redwoodjs.com). ## *Deploy a Redwood app* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. Now let's generate a new Redwood app and set it up. ```bash theme={null} yarn create redwood-app my-redwood-project cd my-redwood-project ``` You can check out the [Redwood Quick Start guide](https://redwoodjs.com/docs/quick-start) to get your development environment setup and to generate some data models. Once you're happy with your app, we can deploy it: ```bash cmd theme={null} cd <%= app_name %> fly launch ``` ```output output theme={null} Creating app in /Users/me/<%= app_name %> Scanning source code Detected a <%= detected %> app ? App Name (leave blank to use an auto-generated name): <%= app_name %> ? Select organization: flyio (flyio) ? Select region: mad (Madrid, Spain) Created app <%= app_name %> in organization soupedup ? Would you like to deploy now? Yes ==> Validating app configuration --> Validating app configuration done Services TCP 80/443 ⇢ 8080 Remote builder fly-builder-little-glitter-8329 ready ... 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 2 passing] --> v0 deployed successfully ``` # Deploy a Remix App Source: https://docs.fly.io/js/frameworks/remix Deploy a Remix app to Fly.io. fly launch detects Remix, generates a Dockerfile and fly.toml, and deploys your app to a Fly Machine. ![Illustration by Annie Ruygt of a color wave accross the sky](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/remix.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=67b3fe455ece6b53b67f7d7f9240717c) In this guide we’ll learn how to deploy a [Remix](https://remix.run/) application on Fly.io. We'll be using the standard web application generated by [Remix](https://remix.run/). This is a bare-bones app with no database. ## *Deploy a Remix app* We'll assume you have NodeJS installed already and can run `npm`. We recommend using `npm` over `yarn`, as `npm` is what Remix [references in their documentation](https://remix.run/docs/en/main/tutorials/blog). First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. Now let's generate a new Remix app. When asked where you want to deploy, pick `Fly.io`. ```bash cmd theme={null} npx create-remix@latest ``` ```output output theme={null} Need to install the following packages: create-remix@2.10.0 Ok to proceed? (y) y remix v2.10.0 💿 Let's build a better website... dir Where should we create your new project? ./my-remix-app ◼ Using basic template See https://remix.run/guides/templates for more ✔ Template copied git Initialize a new git repository? Yes deps Install dependencies with npm? Yes ✔ Dependencies installed ✔ Git initialized done That's it! Enter your project directory using cd ./my-remix-app Check out README.md for development and deploy instructions. Join the community at https://rmx.as/discord ... ``` Now you're ready to deploy: ```bash cmd theme={null} cd my-remix-app fly launch ``` ```output output theme={null} Scanning source code Detected a Remix app Creating app in .../my-remix-app We're about to launch your Remix app on Fly.io. Here's what you're getting: Organization: Jane Developer (fly launch defaults to the personal org) Name: my-remix-app-restless-moon-4797 (generated) Region: Ashburn, Virginia (US) (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: (not requested) Tigris: (not requested) ? Do you want to tweak these settings before proceeding? No Created app 'my-remix-app-restless-moon-4797' in organization 'personal' Admin URL: https://fly.io/apps/my-remix-app-restless-moon-4797 Hostname: my-remix-app-restless-moon-4797.fly.dev installing: npm install @flydotio/dockerfile@latest --save-dev added 22 packages, and audited 841 packages in 2s 258 packages are looking for funding run `npm fund` for details found 0 vulnerabilities create Dockerfile execute flyctl secrets set SESSION_SECRET Secrets are staged for the first deployment Wrote config file fly.toml Validating /Users/rubys/tmp/my-remix-app/fly.toml ✓ Configuration is valid ==> Building image . . . Visit your newly deployed app at https://my-remix-app-restless-moon-4797.fly.dev/ ``` That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * [`fly logs`](/flyctl/cmd/fly_logs) - Tail your application logs * [`fly status`](/flyctl/cmd/fly_status) - View your app's current deployment status * [`fly ssh console`](/flyctl/cmd/fly_ssh_console) - Open a terminal on your VM * [`fly deploy`](/flyctl/cmd/fly_deploy) - Deploy the application after making changes # Deploy a SvelteKit App Source: https://docs.fly.io/js/frameworks/svelte Deploy a SvelteKit app to Fly.io. fly launch generates a Dockerfile and fly.toml for your SvelteKit project and deploys it to a Fly Machine. In this guide we’ll learn how to deploy a [SvelteKit](https://kit.svelte.dev/) application on Fly.io. We'll be using the standard web application generated by [SvelteKit](https://kit.svelte.dev/). This is a bare-bones app with no database. ## *Generate the SvelteKit app* We'll assume you have NodeJS installed already and can run `npm`. We recommend using `npm` over `yarn`, as `npm` is what SvelteKit [references in their documentation](https://kit.svelte.dev/docs/creating-a-project). Let's create a new project by running `npm create`. This will scaffold a new project asking you if you'd like to set up some basic tooling such as TypeScript. ```bash cmd theme={null} npm create svelte@latest hello-sveltekit ``` ```output output theme={null} Need to install the following packages: create-svelte@5.0.2 Ok to proceed? (y) y create-svelte version 5.0.2 ┌ Welcome to SvelteKit! │ ◇ Which Svelte app template? │ SvelteKit demo app │ ◇ Add type checking with TypeScript? │ Yes, using JavaScript with JSDoc comments │ ◇ Select additional options (use arrow keys/space bar) │ none │ └ Your project is ready! ... Next steps: 1: cd hello-sveltekit 2: npm install (or pnpm install, etc) 3: git init && git add -A && git commit -m "Initial commit" (optional) 4: npm run dev -- --open To close the dev server, hit Ctrl-C Stuck? Visit us at https://svelte.dev/chat ... ``` You can run `npm install` to install the necessary dependencies and proceed with configuring the adapter. ## *Configuring the Adapter* Before you can deploy your SvelteKit app, you need to *adapt* it for your deployment target. Adapters are small plugins that take the built app as input and generate output for deployment. In this example, to deploy your app on Fly.io, we'll install the Node Adapter. You can install it by executing the command `npm i -D @sveltejs/adapter-node`. Then, replace the first line in your `svelte.config.js` file with the following: ```javascript theme={null} import adapter from '@sveltejs/adapter-node'; ``` ## *Configuring the `start` command* The "start" command is used to initiate your application and should be included before deploying. To add the command, locate the `scripts` section within the `package.json` file and insert `"start": "node build",` at the beginning of the section. As a result, your `scripts` section in the `package.json` file should appear as follows: ```json theme={null} "scripts": { "start": "node build", ... } ``` ## *Building the Application* Building a SvelteKit app happens in two stages, both of which are executed when the command `npm run build` is run. Firstly, Vite undertakes the creation of an optimized production build that encompasses your server code, browser code, and, if applicable, your service worker. Prerendering is carried out during this stage if deemed necessary. Secondly, an adapter is employed to fine-tune this production build to align with your specific target environment. ## *Preview your Application* Once the build process is complete, you can preview your production build locally by executing the command `npm run preview`. ## *Install Flyctl and Login* We are ready to start working with Fly.io, and that means we need `flyctl`, our CLI app for managing apps on Fly.io. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly.io](/getting-started/sign-up-sign-in). ## *Deploy the app on Fly.io* Each Fly App needs a `fly.toml` file to tell the system how we'd like to deploy it. That file can be automatically generated with `fly launch`. This command will also generate a Dockerfile for deployment. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Creating app in \fly\guides\hello-sveltekit Scanning source code Detected a NodeJS app ? Choose an app name (leave blank to generate one): hello-sveltekit ? Select Organization: demo (demo) ? Select region: ord (Chicago, Illinois (US)) App will use 'ord' region as primary Created app 'hello-sveltekit' in organization 'demo' Admin URL: https://fly.io/apps/hello-sveltekit Hostname: hello-sveltekit.fly.dev installing: npm install @flydotio/dockerfile --save-dev ... Wrote config file fly.toml Validating \fly\guides\hello-sveltekit\fly.toml Platform: machines ✓ Configuration is valid If you need custom packages installed, or have problems with your deployment build, you may need to edit the Dockerfile for app-specific changes. If you need help, please post on https://community.fly.io. Now: run 'fly deploy' to deploy your Node app. ... ``` ## *Inside `fly.toml`* The `fly.toml` file now contains a default configuration for deploying your app. In the process of creating that file, `flyctl` has also created a Fly.io application slot of the same name, "hello-sveltekit". If we look at the `fly.toml` configuration file we can see the name in there: ```toml theme={null} app = "hello-sveltekit" primary_region = "ord" [http_service] internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ["app"] ... ``` The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name/value at the start. That name will be used to identify the application to the Fly.io platform. The rest of the file contains settings to be applied to the application when it deploys. ## Deploying to Fly.io We are now ready to deploy our containerized app to Fly.io: ```bash theme={null} fly deploy ``` This will get the app name `hello-sveltekit` from our `fly.toml` and start the process of deploying our application to Fly.io using the Dockerfile. `fly` will return you to the command line when it's done. ## *Viewing the Deployed App* If you want to find out more about the deployment. The command `fly status` will give you all the essential details. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = hello-sveltekit Owner = personal Hostname = hello-sveltekit.fly.dev Image = hello-sveltekit:deployment-01H4MDR3GVFX20X5DNVZS4BPWK Platform = machines Machines PROCESS ID VERSION REGION STATE CHECKS LAST UPDATED app 91857500000083 1 ord started 2023-07-06T01:49:28Z app e2865100000086 1 ord stopped 2023-07-06T01:50:04Z ``` ## *Connecting to the App* The quickest way to browse your newly deployed application is with the `flyctl apps open` command. ```bash cmd theme={null} flyctl apps open ``` ```output output theme={null} Opening https://hello-sveltekit.fly.dev/ ``` Your browser will be sent to the displayed URL. ## *Bonus Points* If you want to know what IP addresses the app is using, try `fly ips list`: ```bash cmd theme={null} fly ips list ``` ```out out theme={null} VERSION IP TYPE REGION CREATED AT v6 2a09:8280:1::69:37fd public global 18m55s ago v4 66.241.125.223 public (shared) ``` ## Arrived at Destination You have successfully built, deployed, and connected to your first SvelteKit application on Fly.io. # JavaScript on Fly.io Source: https://docs.fly.io/js/index Run JavaScript apps on Fly.io: start with the Node.js starter app, then use The Basics for production setup, and get help from the community forum. ![Picture of a JavaScript book on a bookshelf with other books](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/js-bookshelf.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=8db570cf840419678a226dee8687bf40) Fly is a great place to run JavaScript applications, especially if you plan on running them on multiple servers around the world so your users have a fast, snappy, low-latency experience. This becomes particularly important with applications that make use of Server-Side Rendering or React Server Components. If you have questions or comments about running JavaScript applications on Fly.io, create a new topic in [the Fly.io community Forum](https://community.fly.io/) and tag it with "nodejs" so the right people from Fly.io and our community can give you the support you need. ## Getting Started Run through the [Starter Node.js App](https://fly.io/blog/vanilla-candy-sprinkles/) to get a feel for what it's like to deploy to Fly. If you're short on time, just do the first section and you'll have a Node.js app running in just a few minutes. ## The Basics [The Basics](/js/the-basics) covers everything you need to set up, run, and manage production Node.js apps on Fly. These documents also identify some common problems you may encounter and provides some pointers on how to resolve them. # Prisma Source: https://docs.fly.io/js/prisma/index Pick a database for a Prisma app on Fly.io. Compares SQLite and PostgreSQL for scaling, recommends starting with Postgres, and links setup for each. [Prisma](https://www.prisma.io/) is a popular Object-Relational Mapping (ORM) library. You can find plenty of ready to run [Fullstack examples](https://github.com/prisma/prisma-examples?tab=readme-ov-file#prisma-orm) on the Prisma site using Next.js, Nuxt, Remix and Sveltekit. Prisma supports a [wide range of Databases](https://www.prisma.io/docs/orm/overview/databases), for deployment to Fly.io, [PostgreSQL](https://www.prisma.io/docs/orm/overview/databases/postgresql) and [Sqlite](https://www.prisma.io/docs/orm/overview/databases/sqlite) are recommended. Sqlite is more convenient for development and scales well vertically. PostgreSQL is fine for development and scales well horizontally. If you are not sure which to pick, start with PostgreSQL. It is also possible to start with SQLite and convert later. For most applications using one of these two databases, [fly launch](/reference/fly-launch) will pick a configuration that will get you up and running. For more information, click on one of the following links: * [SQLite](./sqlite) * [PostgreSQL](./postgres) # Prisma with PostgreSQL Source: https://docs.fly.io/js/prisma/postgres Deploy a Prisma app with PostgreSQL on Fly.io: release_command runs prisma migrate deploy, seeds run at launch, and pgloader converts an old SQLite DB. [PostgreSQL](https://www.postgresql.org/) is a safe choice for production. Determination of the database provider and location of the database will be done by parsing your `prisma/schema.prisma` file: ```config theme={null} datasource db { provider = "postgresql" url = env("DATABASE_URL") } ``` `fly launch` will create a [PostgreSQL database](/unmanaged-postgres) suitable for development. For production, see [Fly.io Managed Postgres](/postgres). ## Dockerfile If your application does not have a `Dockerfile`, `fly launch` will create a `Dockerfile` for you. If at a later time you would like to replace your `Dockerfile`, you can do so by running: ``` npx @flydotio/dockerfile ``` See [fly-apps/dockerfile-node](https://github.com/fly-apps/dockerfile-node?tab=readme-ov-file#overview) for a list of available options. ## Migrations and seeds [prisma migrate](https://www.prisma.io/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate) will be run on every deploy, applying the migrations found in your `prisma/migrations` directory against your production database. The command to be run is controlled by your [`fly.toml`](/reference/configuration#run-one-off-commands-before-releasing-a-deployment): ```toml theme={null} [deploy] release_command = 'npx prisma migrate deploy' ``` If a [seed command](https://www.prisma.io/docs/orm/prisma-migrate/workflows/seeding) is found in your `package.json`, it will be run once, at `fly launch` time, immediately after the first migration. ## Conversion from SQLite Converting from SQLite starts by updating your `prisma/schema.prisma` to match what you see at the top of this post. Next, remove all your existing migrations in your `prisma/migrations` directory and recreate them: ``` rm -rf prisma/migrations export DATABASE_URL=postgres://$USER@localhost/localdb npx prisma migrate dev --name init ``` Remove the `[processes]` and `[mounts]` sections from the `fly.toml` and add the `[deploy]` section you see above. Download your existing SQLite database and use [pgloader](https://pgloader.readthedocs.io/en/latest/ref/sqlite.html) to convert it to postgres: ```bash theme={null} fly sftp get /data/dev.db pgloader sqlite:./dev.db pgsql://$USER@localhost/localdb ``` Import the resulting database. For Fly Postgres use [`fly postgres import`](/flyctl/cmd/fly_postgres_import). Set the `DATABASE_URL` secret using [`fly secrets set`](/flyctl/cmd/fly_secrets_set). Run `fly deploy`. Once you are up and running, you can delete your volume using [`fly volumes destroy`](/flyctl/cmd/fly_volumes_destroy). # Sqlite Source: https://docs.fly.io/js/prisma/sqlite What fly launch sets up for Prisma with SQLite on Fly.io: an auto-extending volume, migrations at boot, Litestream backups to Tigris, and a setup script. Most Prisma demos use [SQLite](https://www.sqlite.org/index.html), as it will get you up and running fast. As an embedded database, it will run fast in production also, some report anywhere from [10 to 600](https://www.youtube.com/watch?v=XcAYkriuQ1o) times faster than PostgreSQL. Sqlite publishes a page of [appropriate uses of SQLite](https://www.sqlite.org/whentouse.html). There are few downsides to be aware of: * You can only have a single writer at a time on a SQLite database. This may not be an issue from a performance perspective as dozens of small writes may still complete faster than dozens of concurrent writes using other databases. * You are effectively limited to a single machine. Machines can scale quite large these days, but requests from around the world will need to make the transit and back to this machine. [LiteFS](/litefs) can provide read replicas. * There will be momentary site unavailability when you deploy a new version. For most applications this will be a few hundred milliseconds. Below is a description of the starting point that `fly launch` will provide for you. ## Dockerfile If your application does not have a `Dockerfile`, `fly launch` will create a `Dockerfile` for you. Determination of the database provider and location of the database will be done by parsing your `prisma/schema.prisma` file: ```config theme={null} datasource db { provider = "sqlite" url = "file:./dev.db" } ``` If you do have a pre-existing `Dockerfile`, the `CMD` to start your application will be overridden in your `fly.toml` in order to run a startup script (as described [below](#setup-script)). These lines will look like: ```toml theme={null} [processes] app = "node dbsetup.js npm run start" ``` If at a later time you would like to replace your `Dockerfile`, you can do so by running: ``` npx @flydotio/dockerfile ``` See [fly-apps/dockerfile-node](https://github.com/fly-apps/dockerfile-node?tab=readme-ov-file#overview) for a list of available options. ## Volume In order to survive restarts and deploys, your database will be placed on a [volume](/volumes/overview). This is controlled by the [mounts section](/reference/configuration#the-mounts-section) in your `fly.toml`: ```toml theme={null} [mounts] source = "data" destination="/data" auto_extend_size_threshold = 80 auto_extend_size_increment = "1GB" auto_extend_size_limit = "10GB" ``` Your volume will start at 1 Gigabyte and grow up to 10 Gigabytes whenever it reaches 80% of capacity. Adjust these values as needed and run `fly deploy` to make the new values effective. ## Migrations and seeds [prisma migrate](https://www.prisma.io/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate) will be run every time your server starts, applying the migrations found in your `prisma/migrations` directory against your production database. If a [seed command](https://www.prisma.io/docs/orm/prisma-migrate/workflows/seeding) is found in your `package.json`, it will be run once, at `fly launch` time, immediately after the first migration. ## Backing up Because SQLite runs on a single machine, it is susceptible to host and volume failures. For this reason [LiteStream](https://litestream.io/) is automatically configured during `fly launch` for Prisma applications that use SQLite, if you confirm that you want Tigris configured: ``` Tigris: private bucket (determined from app source) ? Do you want to tweak these settings before proceeding? No ``` This will cause 5 secrets to be set: `AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT_URL_S3`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY`, and `BUCKET_NAME` And a `litestream.yml` file to be created: ```yaml theme={null} # This is the configuration file for litestream. # # For more details, see: https://litestream.io/reference/config/ # dbs: - path: /data/dev.db replicas: - type: s3 endpoint: $AWS_ENDPOINT_URL_S3 bucket: $BUCKET_NAME path: litestream/dev.db access-key-id: $AWS_ACCESS_KEY_ID secret-access-key: $AWS_SECRET_ACCESS_KEY ``` If we generate a `Dockerfile` for you, installation of Litestream will be included in the `Dockerfile`. Otherwise, [@flydotio/litestream](https://www.npmjs.com/package/@flydotio/litestream) will be added to your `package.json` as a dependency. In the event of a catastrophic failure, simply delete your machine and volume and run `fly deploy`. Your database will be automatically restored from backup. If you decide later you don't want LiteStream backups, delete the storage and secrets: ``` fly storage destroy fly secrets unset AWS_ACCESS_KEY_ID AWS_ENDPOINT_URL_S3 AWS_REGION AWS_SECRET_ACCESS_KEY BUCKET_NAME ``` ## Setup Script Depending on whether or not we generate a `Dockerfile` for you, an `ENTRYPOINT` script named `docker-entrypoint.js` or a setup script named `dbsetup.js` will be created. This script will perform the following functions: * Create a symbolic link from your database location to the volume. This will enable you to continue to develop and test on your local machine and still retain persistence of your database in production. * Restore your database from LiteStream if it doesn't currently exist. * Run database migrations * Run your seed command if the database doesn't exist * Run [`litestream replicate`](https://litestream.io/reference/replicate/) to monitor & continuously replicate SQLite database. * Launch your application Depending on your application and what framework it uses, this script may perform other functions. Feel free to adapt this script as needed. # Deploy a Shopify App Source: https://docs.fly.io/js/shopify Deploy and host a Shopify app on Fly.io. fly launch reads your Shopify app config, sets secrets and env vars, and deploys it in one command. Deploy and host your Shopify app on Fly.io. `fly launch` has built-in support for [Shopify apps](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). Check out a video from Shopify running through the deploy process [here](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: ``` 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). The value of `SHOPIFY_API_SECRET` will be set as a [fly secret](/apps/secrets). The remainder will be placed into your `fly.toml`: ```toml theme={null} [env] PORT = '3000' SCOPES = 'write_products' SHOPIFY_API_KEY = '…' SHOPIFY_APP_URL = 'https://….fly.dev' ``` > [!NOTE] > > 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). 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](/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](/reference/configuration#the-http_service-section) will prevent your Machines from being stopped all together. You can also [Scale Machine CPU and RAM](/launch/scale-machine), and [Scale the number of Machines](/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 unavailability 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](/launch/deploy#deployment-strategy) are the default, avoiding any downtime. Additional information is available on [deploying Prisma apps on Fly.io](./prisma/). # JavaScript: The Basics Source: https://docs.fly.io/js/the-basics JavaScript basics on Fly.io for Express, Fastify and Node apps: Dockerfiles, dependencies, databases, volumes, Tigris storage, ports, secrets, scaling. If you are running a small/simple application making use of a higher level framework like [Next.js](/js/frameworks/nextjs/) or [Remix](/js/frameworks/remix) the information provided on these pages may be useful as background information. Other applications using things like [Express](https://expressjs.com/), [Fastify](https://fastify.dev/), or even [http.server](https://nodejs.org/api/http.html#class-httpserver) may require additional set up. These guides will help you get through the basics of setting up your JavaScript application with pieces of infrastructure commonly found in medium-to-high complexity JavaScript applications. ## Installing flyctl and logging in In order to start working with Fly.io, you will need `flyctl`, our CLI app for managing apps. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). Once you have logged on, below are a number of topics. Review the ones you want, and in review them in any order. Once you are ready, run: ```bash theme={null} fly deploy ``` ## Dockerfiles Get started by generating a Dockerfile used to build and deploy your application. ## Dependencies Installing needed dependencies, and addressing common problems. ## Databases Connecting your application to a database. ## Volumes Local persistent storage for Fly machines. ## Object Storage Provision a Tigris Bucket and access it via a S3Client ## Listening Ports Connecting your application to the internet. ## Secrets Making credentials available to your production ## Scaling Scaling your application to meet demand # Databases Source: https://docs.fly.io/js/the-basics/database Connect a JavaScript app on Fly.io to a database through DATABASE_URL with Prisma, Drizzle, Knex, pg, sqlite3, or MongoDB, plus SSL and migration advice. Most web servers store data in a database, and there are many databases to chose from. Examples include running [Managed Postgres](/postgres) on fly.io's infrastructure, embedding [SQLite](https://www.sqlite.org/index.html) in your application, or accessing a [MongoDB](https://www.mongodb.com/) database hosted elsewhere. For maximum flexibility, it generally is best if your application doesn't hard code the path to the database, but rather extracts the path from the environment. We recommend, and will by default automatically set, `DATABASE_URL` to this path when we either provide or can detect the database. For cases where an external database is used, you can set this yourself using [`fly secrets set`](/apps/secrets#set-secrets). ## Accessing the DATABASE\_URL value The way you access the `DATABASE_URL` value depends on the module or ORM you are using. For Prisma, ensure your `prisma/schema.prisma` file contains: ```js theme={null} const url = env("DATABASE_URL") ``` For drizzle/sqlite3: ```js theme={null} const db = drizzle(new Database(new URL(process.env.DATABASE_URL).pathname)); ``` For drizzle/postgres: ```js theme={null} const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const db = drizzle(pool) ``` For knex/sqlite3: ```js theme={null} client: 'sqlite3', connection: { filename: new URL(process.env.DATABASE_URL).pathname }, ``` For knex/pg: ```js theme={null} client: 'pg', connection: process.env.DATABASE_URL ``` for sqlite3: ```js theme={null} const db = new sqlite3.Database(new URL(process.env.DATABASE_URL).pathname); ``` for postgres: ```js theme={null} const client = new pg.Client({connectionString: process.env.DATABASE_URL}); ``` for mongodb: ```js theme={null} const client = new mongodb.MongoClient(process.env.DATABASE_URL); ``` ## SSL/TLS If your database is hosted external to fly.io, you generally will want to connect to it securely. The way to do so varies by database and adapter. An example for postgres can be found at [node-postgres features > ssl](https://node-postgres.com/features/ssl). Equally important, if your database is hosted within the [Fly.io private network](/networking/private-networking), you will *not* want to connect with SSL/TLS connection options. ## Migrations In order to use a database we need to load and evolve a schema. This often involves running a tool like Prisma's [migrate](https://www.prisma.io/docs/concepts/components/prisma-migrate) tool. For databases other than Sqlite3, this is best done as a one-off command run in a temporary VM. This can be accomplished by adding a [`release_command`](/reference/configuration#run-one-off-commands-before-releasing-a-deployment) in your `fly.toml` file. For sqlite3, a different approach is required as the migration will need to be run on each machine that has a volume mounted. This can be accomplished by creating a small script that first runs the migrate command then run the web server start script. You can place this script in your source tree, and then use the following command to replace the CMD in the Dockerfile: ```bash theme={null} npx @flydotio/dockerfile --cmd=file ``` # Dependencies Source: https://docs.fly.io/js/the-basics/dependencies Fix Node.js and Bun dependency problems when deploying to Fly.io, including missing, miscategorized, legacy peer, and native npm modules, using dockerfile-node. Whether you are using [bun](https://bun.sh/), [pnpm](https://pnpm.io/), [npm](https://docs.npmjs.com/cli/v9/commands/npm) or [yarn](https://classic.yarnpkg.com/lang/en/), most Node.js and Bun applications depend on [npm](https://www.npmjs.com/) modules, and record their dependencies in a [package.json](https://docs.npmjs.com/cli/v9/configuring-npm/package-json) file. If you are running a Deno app, this page does not apply, instead refer to [Deploy a Deno App](/js/frameworks/deno) for more information. ## Missing dependencies If your application works locally but doesn't work when deployed this may be due to dependencies which are installed globally rather than being enumerated in the package.json file. In order to deploy an application, it needs to be self-contained. You can resolve this by adding the dependency to your `package.json`, typically by using a command such as `yarn add` or `npm install`. ## Mis-categorized dependencies Many `package.json` files have two lists of dependencies: `dependencies` and `devDependencies`. `devDependencies` are intended to list dependencies which are only needed at build time and not when deployed. Sometimes dependencies are miscategorized, for example a database tool needed to perform migration may be listed as a `devDependency` despite the fact that such a tool is needed to deploy. It is generally best to correct these errors by moving the dependency to the right place in the `package.json`. An alternative when this isn't feasible or desired is to regenerate the dockerfile to include *all* dependencies at deployment: ```bash theme={null} npx @flydotio/dockerfile --dev ``` ## Legacy peer dependencies Some `package.json` files have a third set of dependencies `peerDependencies`. As tooling support for this varies, this information can become stale and out of sync, causing builds to fail. While correcting these dependencies may work for a while, often times it becomes necessary to indicate that these dependencies are *legacy*. The way to do this varies based on the package manager, and dockerfile-node can take care of this for you: ```bash theme={null} npx @flydotio/dockerfile --legacy-peer-deps ``` ## Native modules Some npm modules are *native* modules which may require additional libraries to be installed, either at build time, or at deploy time, or both. ``` npx @flydotio/dockerfile --add-base=libxml2 npx @flydotio/dockerfile --add-build=babel npx @flydotio/dockerfile --add-deploy=fontconfig ``` If you find yourself needing to do this, and your need is a consequence of using a native module, please open an [issue](https://github.com/fly-apps/dockerfile-node/issues) so that other users with the same dependency can have this taken care of for them automatically. # Dockerfiles Source: https://docs.fly.io/js/the-basics/dockerfiles Deploy Node.js, Bun, and Deno apps on Fly.io with a Dockerfile, either your own or one generated by fly launch and adjusted with dockerfile-node options. The recommended way to deploy Bun, Deno, and Node.js applications is via [Dockerfiles](https://docs.docker.com/engine/reference/builder/). You can provide your own Dockerfile or you can let Fly.io's [dockerfile generator](https://github.com/fly-apps/dockerfile-node#overview) produce one for you. Launching applications on Fly can be as simple as running: ```bash theme={null} fly launch ``` In most cases you won't ever need to edit the Dockerfile `fly launch` provides for you directly. Instead you will be able to use one or more of the many [options](https://github.com/fly-apps/dockerfile-node#options) to make changes. For example: ```bash theme={null} npx @flydotio/dockerfile --add=dnsutils ``` # Listening Ports Source: https://docs.fly.io/js/the-basics/listening-ports Make a Node.js app reachable on Fly.io by matching internal_port in fly.toml to your app port, listening on 0.0.0.0, and adding services for extra ports. When you run your application locally you typically test it in your browser by navigating to a page like `http://localhost:3000/`. The number after the colon is called a *port*. For most Node.js applications this port is 3000, for some it is 8080, but it can be any number between 1024 and 65535. If you are using a high level framework, it will generally take care of this for you. For lower level frameworks, you likely have code such as the following, typically in a file named `server.js` or `server.ts`: ```javascript theme={null} const hostname = "0.0.0.0"; const port = process.env.port || 3000; app.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); ``` If you don't have this configured correctly, you may see messages like the following when you launch: ```text theme={null} WARNING The app is not listening on the expected address and will not be reachable by fly-proxy. You can fix this by configuring your app to listen on the following addresses: - 0.0.0.0:3000 ``` ## Correcting the port If you look for a file named `fly.toml`, you will see something like the following: ```toml theme={null} [http_service] internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ["app"] ``` The number specified by `internal_port` needs to match the port used to start your application. It doesn't matter whether you change your application or the contents of this file to make these numbers match. ## Correcting the host Another common problem is that the application is set up to listen only for connections originating from `localhost` instead of connections from the internet. If you have code like the above, change the value of `hostname` to either `0.0.0.0` or `[::]`. Other frameworks may require you to change command used to start your application: * Fastify applications can be started using `fastify start --address 0.0.0.0` * Gatsby applications can be started using `npx gatsby server -H 0.0.0.0`. ## Exposing multiple ports If you need to expose additional ports, you will need to add a [`services` section](/reference/configuration#the-services-sections) for each additional port. Be sure to review our [Public Network Services](/networking/services) page before proceeding, in particular: * If you want your port other than 80/443 to be available on IPv4 you will need to [allocate a dedicated IPv4 address](/flyctl/cmd/fly_ips_allocate-v4). * If you want to take advantage of our TLS services, you will want to add *"tls"* to the list of connection handlers. # Object Storage Source: https://docs.fly.io/js/the-basics/object-storage Add Tigris S3-compatible object storage to a JavaScript app on Fly.io with fly storage create, then access the bucket from S3Client with no extra configuration. [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 overridden in the launch UI: ![Tigris Launch-UI](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/tigris-launch-ui.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=5c791fa3e123adacd987604e64ef6426) ## Adding Tigris to an existing application? Create a storage bucket using `fly storage create`: ```bash cmd theme={null} fly storage create ``` ```output output theme={null} ? Choose a name, use the default, or leave blank to generate one: Your Tigris project (xxx) is ready. See details and next steps with: https://fly.io/docs/tigris/ Setting the following secrets on xxx: AWS_ACCESS_KEY_ID: tid_xxx AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev AWS_REGION: auto AWS_SECRET_ACCESS_KEY: tsec_xxxxx BUCKET_NAME: xxx Secrets are staged for the first deployment ``` ## Usage Once provisioned, no further configuration is required to access your bucket using the `S3Client` class: ```javascript theme={null} const S3 = new S3Client() ``` ## Demo [`node-dictaphone`](https://github.com/fly-apps/node-dictaphone) contains a demo application that captures audio, uploads the result to a Tigris bucket, where it can be selected and replayed: ``` 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](/tigris) page. Highlights include public buckets, migrating to Tigris with shadow buckets, pricing, and AWS API compatibility. # Scaling Source: https://docs.fly.io/js/the-basics/scaling Scale a JavaScript app on Fly.io by choosing memory and swap, using the Node.js cluster module for multiple CPU cores, and adding more Machines. Scaling an application is a complex subject with many variables, in this page we will focus on a number of factors that are largely universal: number of CPU cores, memory, and both the number and placement of virtual machines. Not covered here is scaling of databases or scaling of volumes. For information on costs, see [Fly App Pricing](/about/pricing). ## Memory RAM is a precious commodity - both to those on Hobby plans who want to remain within or near the free allowances, and to apps that want to scale to be able to handle a large number of concurrent connections. At some point you may find that you need more memory. There are two types: real and virtual. Real is faster, but more expensive. Virtual (swap) is slower and generally free. Some starting point recommendations: * 256MB is enough to run small applications with few users * 512MB is a better place to start if you are doing image processing or have a moderate number of users. * 1024MB is a minimum if you are running an application that uses puppeteer or otherwise makes use of chrome's rendering engine. Once you have settled on a size, some links to get you started: * [`fly scale memory`](/flyctl/cmd/fly_scale_memory) * [`swap_size_mb`](/reference/configuration#swap_size_mb-option) These options are not mutually exclusive. A good starting point may be 512MB of RAM and 1024MB of swap. Note: if you do elect to use swap, Node.js may not make full use of the extra memory, resulting in an `JavaScript heap out of memory` error, You can instruct it to use more by setting an environment variable in your `fly.toml`: ```toml theme={null} [env] NODE_OPTIONS='--max-old-space-size=4096' ``` See the [Node.js documentation](https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-megabytes) for more information. ## CPU Cores By default, JavaScript applications (whether they be Node.js, Deno, or Bun) run on a single operating system thread and therefore won't benefit from running with multiple cores. Node.js has a [cluster](https://nodejs.org/api/cluster.html) module that can be used to address this. See [Run a Node.js HTTP Express.js Server on Multiple CPU Cores](https://coderrocketfuel.com/article/run-a-node-js-http-express-js-server-on-multiple-cpu-cores) for an example on how to use this module. Once your application is coded to take advantage of multiple cores, run `fly machines list` to see a list of your machines, and then run [`fly machine update --vm-cpus`](/flyctl/cmd/fly_machine_update) to update the number of CPUs a single machine. ## Virtual Machines For most JS applications the key to scaling will be in creating more virtual machines, each with enough memory and CPU cores to handle a unit of workload. More information on that topic can be found on the [Scale the Number of Machines](/launch/scale-count) page. # Secrets Source: https://docs.fly.io/js/the-basics/secrets Set production secrets for a JavaScript app on Fly.io with fly secrets set or by importing a dotenv file, then read them via process.env, Deno.env, or Bun.env. Running an application often involves a number of credentials, containing such things as database passwords, S3 buckets, and session encryption keys. During development you will typically use [environment files](https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--env-fileconfig). For security reasons, these files are generally excluded from being committed to version control or being deployed to production. This means that you need another mechanism for setting up your production environment. For Fly.io, that mechanism is [secrets](/apps/secrets). There are two ways to set secrets. You can use the [fly secrets set](/flyctl/cmd/fly_secrets_set) command: ``` fly secrets set SECRET_KEY=YOURSECRETKEYGOESHERE ``` Or you can import your dotenv file wholesale using [fly secrets import](/flyctl/cmd/fly_secrets_import): ``` cat .dotenv | fly secrets import ``` Both commands will restart your application if it has previously been deployed. Pass the `--stage` option if you would rather deploy these changes later. Once deployed, your application can access secrets using [process.env](https://nodejs.org/dist/latest-v8.x/docs/api/process.html#process_process_env), [Deno.env](https://docs.deno.com/runtime/manual/basics/env_variables), or [Bun.env](https://bun.sh/guides/runtime/read-env). # JavaScript: Volumes Source: https://docs.fly.io/js/the-basics/volumes Persist files and SQLite data for a JavaScript app on Fly.io by creating a volume for each Machine, mounting it in fly.toml, and writing to the mount path. If your application writes configuration, session, or user data to the file system and you want this data to persist across restarts you will need a [fly volume](/volumes). This doesn't apply if your data is stored in a database elsewhere (for example in PostgreSQL, MySQL, or MongoDB), but does apply if you are using Sqlite3. ## Creating a volume Use [fly volumes create](/flyctl/cmd/fly_volumes_create) to create a volume. You can specify a region and a size (in gigabytes). Create one for each machine that you will be deploying as volumes can't be shared between machines. You will want to use the same volume name for each volume if these volumes will be mounted by separate instances of the same application. ## Mounting a volume When you created a volume, you gave it a name. Modify your `fly.toml` to contain a [mounts section](/reference/configuration#the-mounts-section) associating that source name with a destination file path: ```toml theme={null} [mounts] source = "myapp_data" destination = "/data" processes= ["app"] ``` The `processes` section is optional if you only have one process defined in your toml. ## Accessing the volume Modify your application to read and write from subdirectories of the destination path you provided in your `fly.toml`. In the case of Sqlite3, set the `DATABASE_URL` value to include this path. # Create an FKS cluster Source: https://docs.fly.io/kubernetes/clusters Create a Fly Kubernetes cluster with fly ext k8s create, get its kubeconfig, and run kubectl over WireGuard. FKS is in closed beta. > [!WARNING] > > Fly Kubernetes is in closed beta and not recommended for critical production usage. To report issues or provide feedback, email us at [beta@fly.io](mailto:beta@fly.io). To create a Kubernetes cluster, run: ```bash theme={null} fly ext k8s create [--name | --org | --region ] ``` Once a cluster is provisioned, it will return a kubeconfig that can be used to connect to your cluster's Kubernetes API server using kubectl. For example: ```yaml theme={null} apiVersion: v1 clusters: - name: fks-flyio-fksdemo cluster: certificate-authority-data: ... server: https://fks-flyio-fksdemo.flycast:6443 extensions: ... ... contexts: - context: cluster: fks-flyio-fksdemo user: default name: default current-context: default kind: Config preferences: {} users: - name: default user: ... ... ``` Your cluster is accessible over your organization's [private WireGuard network](/networking/private-networking). To connect to your cluster, you need a WireGuard configuration. Follow the [Private Network VPN instructions](/networking/private-networking#private-network-vpn) to set up a permanent WireGuard connection to your Fly.io IPv6 private network. Once set up, you can use `kubectl`: ``` > kubectl get ns NAME STATUS AGE kube-public Active 22d kube-node-lease Active 22d default Active 22d kube-system Active 22d ``` ## Related topics * [Connect to an FKS cluster](/kubernetes/connect-clusters) * [Configure FKS Services](/kubernetes/services) # Connect to an FKS cluster Source: https://docs.fly.io/kubernetes/connect-clusters Connect kubectl to an FKS cluster: set up a WireGuard VPN, list clusters with fly ext k8s ls, and save credentials with fly ext k8s save-kubeconfig. > [!WARNING] > > Fly Kubernetes is in closed beta and not recommended for critical production usage. To report issues or provide feedback, email us at [beta@fly.io](mailto:beta@fly.io). Fly Kubernetes clusters are accessible over an organization's [private WireGuard network](/networking/private-networking). To connect to a cluster, you need a WireGuard configuration. Follow the [Private Network VPN instructions](/networking/private-networking#private-network-vpn) to set up a permanent WireGuard connection to your Fly.io IPv6 private network. To connect to your cluster, you need a kubeconfig file. These are generated when your [cluster is created](/kubernetes/clusters). They can also be retrieved using `flyctl`. First, we need our cluster name: ``` > fly ext k8s ls NAME ORG PRIMARY REGION fks-flyio-fksdemo flyio iad ``` Then, we can use `flyctl` to save our kubeconfig to disk. It is automatically saved with the name `kubeconfig` in the current directory ``` > fly ext k8s save-kubeconfig > ls kubeconfig somefile.txt ``` With the kubeconfig file, you can connect to your cluster. To make life easier, you can move the kubeconfig file to the default location kubectl searches for it, `$HOME/.kube`, under the name `config`. Alternatively set the environment variable `KUBECONFIG` to the path to the kubeconfig file ``` export KUBECONFIG=/path/to/kubeconfig ``` Once set up, you can use `kubectl`: ``` > kubectl get ns NAME STATUS AGE kube-public Active 22d kube-node-lease Active 22d default Active 22d kube-system Active 22d ``` ## Related topics * [Create an FKS cluster](/kubernetes/clusters) * [Configure FKS Services](/kubernetes/services) # Fly Kubernetes features Source: https://docs.fly.io/kubernetes/fks-features How Fly Kubernetes runs on Fly Machines, Fly Volumes, and WireGuard, its token auth and security, and unsupported features like DaemonSets. ![Illustration by Annie Ruygt of a ship's wheel](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/k8s-features.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=c058e70f0f2f87c9ec6b3091b3da81e2) Fly Kubernetes benefits from the features of the Fly.io platform. ## Fly.io infrastructure FKS is built on top of Fly.io infrastructure: * Compute is backed by [Fly Machines](/machines), our fast-launching VMs built with Firecracker. * Volumes are handled with [Fly Volumes](/volumes); local, fast NVMe drives. * Networking is built on our [internal WireGuard mesh](/networking/private-networking#private-network-vpn) with routing performed by Fly Proxy. ## Cluster scalability Pods are scheduled across our fleet of bare-metal servers, not limited to a specific node. Workloads are automatically distributed across the fleet, enabling a cluster to scale with ease. ## Security With Fly Kubernetes, you get all the security benefits of our platform. * Private network traffic flows over our internal WireGuard mesh, ensuring it is encrypted. * FKS clusters are secured within a private VPN. * Compute is built on Firecracker microVMs — lightweight and secure virtual machines. This provides improved isolation guarantees ensuring your application code is safe. * Volumes are encrypted at rest for additional protection of the data on the volume. * Secrets are automatically stored in an encrypted vault. ## Simple and secure client authentication Connect to your cluster using kubectl. Clients are authenticated with our API tokens. They expire after an hour and are rotated automatically after expiration. ## Not supported There are a few features we don't support yet but are part of the roadmap: * Sidecars and init processes * EmptyDir volumes * Horizontal pod autoscaling * Network policies * CronJob Features we don't support: * Node affinity * Inter-pod (anti) affinity * DaemonSets ## Pricing See the [pricing page](/about/pricing#fly-kubernetes) for information on FKS pricing. # Fly Kubernetes Quickstart Source: https://docs.fly.io/kubernetes/fks-quickstart Spin up an FKS cluster, connect over WireGuard, and deploy an nginx Deployment with kubectl, plus the pod and container fields FKS doesn't support. ![Illustration by Annie Ruygt of a figure sailing a ship with a balloon overhead](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/kubernetes.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=fa3cf263af1e4f2382d6d7c90f56ee59) > [!NOTE] > > Fly Kubernetes is in closed beta and not recommended for critical production usage. To report issues or provide feedback, email us at [beta@fly.io](mailto:beta@fly.io). To create a Kubernetes cluster, run: ```bash theme={null} fly ext k8s create ``` This will start the provisioning process. You'll be asked for a cluster name, the organization, and the region in which to create the cluster. Once a cluster is provisioned, it will return a kubeconfig that can be used to connect to your cluster's Kubernetes API server using kubectl. For example: ```yaml theme={null} apiVersion: v1 clusters: - name: fks-flyio-fksdemo cluster: certificate-authority-data: ... server: https://fks-flyio-fksdemo.flycast:6443 extensions: ... ... contexts: - context: cluster: fks-flyio-fksdemo user: default name: default current-context: default kind: Config preferences: {} users: - name: default user: ... ... ``` We're going to save the output into a file named `kubeconfig` and create an environment variable `KUBECONFIG` that points to the file: ```bash theme={null} export KUBECONFIG=/path/to/kubeconfig/file ``` Our cluster is accessible over our organization's [private WireGuard network](/networking/private-networking). To connect to our cluster, we need a WireGuard configuration. Follow the [Private Network VPN instructions](/networking/private-networking#private-network-vpn) to set up a permanent WireGuard connection to your Fly.io IPv6 private network. From there, you can use standard YAML configuration to drive Kubernetes. Here's a simple deployment example from the [Kubernetes official documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#creating-a-deployment): ```yaml theme={null} apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 ``` This creates a `ReplicaSet` with 3 nginx pods. Using kubectl, we can apply this: ```bash cmd theme={null} kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml ``` ```output output theme={null} deployment.apps/nginx-deployment created ``` We can see our deployment: ```bash cmd theme={null} kubectl get deployments ``` ```output output theme={null} NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 3/3 3 3 7s ``` We can view the logs of all the pods in the deployment: ```bash cmd theme={null} kubectl logs -l app=nginx ``` ```output output theme={null} Pulling container image registry-1.docker.io/library/nginx:1.14.2 Pulling container image registry-1.docker.io/library/nginx:1.14.2 Pulling container image registry-1.docker.io/library/nginx:1.14.2 Successfully prepared image registry-1.docker.io/library/nginx:1.14.2 (1.597714717s) Successfully prepared image registry-1.docker.io/library/nginx:1.14.2 (1.493016957s) Successfully prepared image registry-1.docker.io/library/nginx:1.14.2 (1.604003393s) Configuring firecracker Configuring firecracker Configuring firecracker ... ``` ## What we don't support * Multi-container pods (coming soon) * Network policies * Horizontal pod autoscaling * Gateway API * Daemon sets * EmptyDir volumes * Interactive pods (`kubectl run --interactive --tty`) On container specs, we don't support the following fields: * `workingDir` * `ports` * `livenessProbe` * `readinessProbe` * `startupProbe` * `resizePolicy` * `stdin` * `stdinOnce` * `tty` * `EnvVarSource.fieldRef` * `EnvVarSource.resourceFieldRef` * `volumeDevices` * `lifecycle` * `terminationMessagePath` * `terminationMessagePolicy` * `imagePullPolicy` * `securityContext` ## Other caveats Right now we don't get logs from a single pod. If you try to get logs from a single pod, you'll get the logs from every pod in the namespace. ## Related topics * [Create an FKS cluster](/kubernetes/clusters) * [Connect to an FKS cluster](/kubernetes/connect-clusters) * [Configure FKS Services](/kubernetes/services) # Fly Kubernetes Source: https://docs.fly.io/kubernetes/index Explore Fly Kubernetes (FKS), a managed Kubernetes service on Fly.io in closed beta: quickstart, features, clusters, connecting, Services, and volumes. > [!WARNING] > > Fly Kubernetes is in closed beta and not recommended for critical production usage. To report issues or provide feedback, email us at [beta@fly.io](mailto:beta@fly.io). Fly Kubernetes (FKS) is a fully managed Kubernetes service built on the Fly.io platform. Deploy Kubernetes clusters without the hassle of managing and operating the control plane. Learn more about [why and how we made FKS](https://fly.io/blog/fks/) in our blog. * **[Fly Kubernetes quickstart](/kubernetes/fks-quickstart):** Get up and running right away with our quickstart guide. * **[Fly Kubernetes features](/kubernetes/fks-features):** An overview of FKS benefits and supported features. * **[Create an FKS cluster](/kubernetes/clusters):** Create an FKS cluster with flyctl. * **[Connect to an FKS cluster](/kubernetes/connect-clusters):** Connect to an FKS cluster on your private network. * **[Configure FKS Services](/kubernetes/services):** Configure ClusterIP and LoadBalancer services. * **[Use volumes with FKS](/kubernetes/using-volumes):** Use Fly Volumes for PersistentVolumes in FKS. # Configure FKS Services Source: https://docs.fly.io/kubernetes/services Expose FKS workloads with ClusterIP and LoadBalancer Services, and set handlers, TLS options, and concurrency limits via service.fly.io annotations. > [!WARNING] > > Fly Kubernetes is in closed beta and not recommended for critical production usage. To report issues or provide feedback, email us at [beta@fly.io](mailto:beta@fly.io). A [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/) exposes applications running on your cluster. Fly Kubernetes supports ClusterIP and LoadBalancer Services. You can create a Service with a service configuration file. Here's an example ClusterIP service: ```yaml theme={null} apiVersion: v1 kind: Service metadata: name: fksdemo-service spec: selector: app: fksdemo ports: - name: http protocol: TCP port: 80 targetPort: 8080 ``` Using kubectl, create the service: ``` > kubectl apply -f service.yaml ``` To view your service: ``` > kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP fdaa:3:dde8:0:1::3a 443/TCP 32h fksdemo-service ClusterIP fdaa:3:dde8:0:1::3b 80/TCP 11s ``` ## Exposing Services publicly Services can be exposed to the public internet with a Service of type LoadBalancer. ``` apiVersion: v1 kind: Service metadata: name: fksdemo-service-public spec: selector: app: fksdemo ports: - name: http protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer ``` The domain name and IP address to access the Service over the internet can be found using kubectl. In the below output, the values are found under the `EXTERNAL-IP` column. ``` > kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP fdaa:3:dde8:0:1::3a 443/TCP 32h fksdemo-service ClusterIP fdaa:3:dde8:0:1::3b 80/TCP 26m fksdemo-service-public LoadBalancer fdaa:3:dde8:0:1::3c fksdemo-service-public.svc.fks-default-vz5dlqz7v4ylrnpq.fly.dev,2a09:8280:1::31:ab8:0,137.66.25.254 80/TCP 8s ``` ## Fly.io connection Handlers Fly.io connection handlers modify your connection before it reaches your application. Learn more about [connection handlers](/networking/services#connection-handlers). Connection handlers are supported with a custom annotation to a Service object. The annotations have the form: ```yaml theme={null} "service.fly.io/-handlers": "" ``` The example below adds an HTTP and TLS handler for port 443. ```yaml theme={null} apiVersion: v1 kind: Service metadata: name: fksdemo-service annotations: "service.fly.io/https-handlers": "http,tls" # can replace https with 443 spec: selector: app: fksdemo ports: - name: http protocol: TCP port: 80 targetPort: 8080 - name: https protocol: TCP port: 443 targetPort: 8080 ``` ### TLS Options [TLS options](/reference/configuration#services-ports-tls_options) are used to configure the TLS settings of a service with a TLS connection handler. Fly's edge will use these settings to terminate TLS for your application. Refer to the [documentation](/reference/configuration#services-ports-tls_options) for details. TLS options are set using custom annotations. There are 3 annotations, one for each setting: * `service.fly.io/-tls-alpn` - Sets the ALPN for negotiation with clients. Values must be comma-separated. * `service.fly.io/-tls-versions` - Sets which TLS versions are allowed. Values must be comma-separated. * `service.fly.io/-tls-default-self-signed` - If true, serves a self-signed certificate if none exists. The most common use case for TLS options is to support gRPC. For example: ```yaml theme={null} apiVersion: v1 kind: Service metadata: name: fksdemo-service annotations: "service.fly.io/https-handlers": "tls" "service.fly.io/https-tls-alpn": "h2" spec: selector: app: fksdemo ports: - name: http protocol: TCP port: 80 targetPort: 8080 - name: https protocol: TCP port: 443 targetPort: 8080 ``` ## Concurrency limits [Concurrency limits](/reference/configuration#services-concurrency) are used to limit the load on your application. By default, the soft limit is set to 20 and the hard limit is set to 25. Learn more about [concurrency limits](/reference/configuration#services-concurrency). They can be configured on your Services using custom annotations. There are 3 annotations used to configure the limits: * `service.fly.io/concurrency-kind` - sets the metric used to measure concurrency * `service.fly.io/concurrency-limit-soft` - sets the concurrency soft limit * `service.fly.io/concurrency-limit-hard` - sets the concurrency hard limit Below is an example of setting this in your Service ```yaml theme={null} apiVersion: v1 kind: Service metadata: name: fksdemo-service annotations: "service.fly.io/concurrency-kind": "connections" "service.fly.io/concurrency-limit-soft": 20 "service.fly.io/concurrency-limit-hard": 25 ``` ## Not supported We currently do not support: * NodePort Services * UDP protocol ## Related topics * [Create an FKS cluster](/kubernetes/clusters) * [Connect to an FKS cluster](/kubernetes/connect-clusters) # Using volumes with FKS Source: https://docs.fly.io/kubernetes/using-volumes Use PersistentVolumeClaims and generic ephemeral volumes in FKS, backed by Fly Volumes, with the flyio-volume and flyio-volume-retain classes. > [!WARNING] > > Fly Kubernetes is in closed beta and not recommended for critical production usage. To report issues or provide feedback, email us at [beta@fly.io](mailto:beta@fly.io). Fly Kubernetes supports [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) and [generic ephemeral volumes](https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes). There are general considerations to keep in mind when using volumes in Fly Kubernetes: * Volumes are built on top of [Fly Volumes](/volumes). They are local NVME drives and require your Pod to be deployed in the same region as the volume. * Only one persistent or ephemeral volume can be mounted at a time. * The maximum storage size available is 500GB. ## Persistent Volumes PersistentVolumes are managed through PersistentVolumeClaims (PVCs). PersistentVolumes (PVs) are tied to the lifecycle of a PersistentVolumeClaim. By default, creating a PVC creates an underlying PV and deleting a PVC deletes the PV bound to it. Below is an example of configuring a PersistentVolume to use with a Pod: ```yaml theme={null} apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: selector: matchLabels: region: iad storageClassName: flyio-volume accessModes: - ReadWriteOncePod resources: requests: storage: 5Gi --- apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx:latest volumeMounts: - mountPath: "/var/www/html" name: mypd resources: limits: memory: "512Mi" cpu: "1" volumes: - name: mypd persistentVolumeClaim: claimName: myclaim ``` In our configuration: * The `storageClassName` must be set to either `flyio-volume` or `flyio-volume-retain`. You can view the available storage classes with `kubectl get sc`. The default StorageClass is `flyio-volume`. * `selector` is optional. It serves a dual purpose. First, it ensures that a PVC is bound to a PV in the region specified in the selector. Second, if a PVC requires dynamic provisioning of the underlying Fly Volume, it is provisioned in the specified region. If not set, the volume is created in the region of the cluster. * `accessModes` only supports `ReadWriteOncePod`. After applying the configuration, you can view the PVC and PV generated by the definition using `kubectl`. ``` > kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE myclaim Bound pvc-r1plo75g59d25l0r 5Gi RWOP flyio-volume 9s ``` ``` > kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-r1plo75g59d25l0r 5Gi RWOP Delete Bound default/myclaim flyio-volume 55s ``` ### Reclaim policy Fly Kubernetes supports both the `Delete` and `Retain` policy for PersistentVolumes. * StorageClass `flyio-volume` has a reclaim policy set to `Delete` * StorageClass `flyio-volume-retain` has a reclaim policy set to `Retain` By default, `flyio-volume` is used. When using the `flyio-volume-retain` StorageClass, you are responsible for deleting the PersistentVolume object and its underlying Fly Volume. The details of the underlying storage are found in the annotations of the PersistentVolume. Using the PV from above ``` > kubectl describe pv pvc-r1plo75g59d25l0r Name: pvc-r1plo75g59d25l0r Labels: region=iad Annotations: fly.io/app: fks-default-vz5dlqz7v4ylrnpq fly.io/region: iad pv.kubernetes.io/provisioned-by: volume.csi.fly.io volume.fly.io/id: vol_r1plo75g59d25l0r Finalizers: [kubernetes.io/pv-protection] StorageClass: flyio-volume Status: Bound Claim: default/myclaim Reclaim Policy: Delete Access Modes: RWOP VolumeMode: Filesystem Capacity: 5Gi Node Affinity: Message: Source: Type: CSI (a Container Storage Interface (CSI) volume source) Driver: volume.csi.fly.io FSType: ext4 VolumeHandle: vol_r1plo75g59d25l0r ReadOnly: false VolumeAttributes: Events: ``` The annotation `fly.io/app` specifies which Fly app the volumes belongs to. The annotation `volume.fly.io/id` gives you the ID of the volume. You can delete the Fly Volume using `flyctl` ``` fly vol rm pvc-r1plo75g59d25l0r -a fks-default-vz5dlqz7v4ylrnpq ``` ## Generic ephemeral volumes Fly Kubernetes supports ephemeral volumes through generic ephemeral volumes. It uses the same underlying PVC and PV machinery. Kubernetes handles creating the PVC and its corresponding PV and deleting both objects when the Pod is deleted. Below is an example of creating a Pod with a generic ephemeral volume: ```yaml theme={null} apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: image: nginx:latest volumeMounts: - mountPath: "/var/www/html" name: scratch-volume resources: limits: memory: "512Mi" cpu: "1" volumes: - name: scratch-volume ephemeral: volumeClaimTemplate: spec: accessModes: - ReadWriteOncePod storageClassName: "flyio-volume" resources: requests: storage: 5Gi ``` For generic ephemeral volumes to work as expected, `storageClassName` must be set to `flyio-volume`. # Deploy a Crystal App Source: https://docs.fly.io/languages-and-frameworks/crystal Deploy a Crystal app to Fly.io and connect it to Postgres. Configure the app with fly launch and ship it with fly deploy. > [!WARNING] > > We haven't yet updated this page for Fly Machines, which are the VMs now at the core of all Fly Apps. You might notice that some features don't work the same way anymore. Visit our [community forum](https://community.fly.io/) if you need help with your app. In this guide we’ll learn how to deploy a Crystal application on Fly.io. ## *Creating a Lucky App* Our example will be using a basic Lucky app with PostgreSQL. We'll assume you already have [lucky+crystal installed](https://luckyframework.org/guides/getting-started/installing). Once lucky is installed, we can create a new project by running the following command: ```bash theme={null} lucky init ``` This will walk you through project options and create a new project. ```bash theme={null} ✗ lucky init Welcome to Lucky v0.29.0 🎉 Project name?: hello-lucky Lucky can generate different types of projects Full (recommended for most apps) ● Great for server rendered HTML or Single Page Applications ● Webpack included ● Setup to compile CSS and JavaScript ● Support for rendering HTML API ● Specialized for use with just APIs ● No webpack ● No static file serving or public folder ● No HTML rendering folders API only or full support for HTML and Webpack? (api/full): full Lucky can be generated with email and password authentication ● Sign in and sign up ● Mixins for requiring sign in ● Password reset ● Token authentication for API endpoints ● Generated files can easily be removed/customized later Generate authentication? (y/n): y ----------------------- Done generating your Lucky project ▸ cd into hello-lucky ▸ check database settings in config/database.cr ▸ run script/setup ▸ run lucky dev to start the server ``` ## *Running The Application* To run the application, first run `script/setup` to install dependencies and create the database. Then run `lucky dev` to start the application. Connect to the address displayed in the console. This should bring you to the "Hello Lucky" page. ## *Install Flyctl and Login* It's time to install `flyctl`, the CLI app for managing apps on Fly.io. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly.io](/getting-started/sign-up-sign-in). ## *Launch the app on Fly* When you run `fly launch` from the newly-created project directory, the launcher provides some defaults for your new app, and gives you the option to tweak the settings. Run: ```sh theme={null} cd hello-lucky fly launch ``` You'll get a summary of the defaults chosen for your app: ```output theme={null} Organization: MyOrgName (fly launch defaults to the personal org) Name: hello-lucky (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes Opening https://fly.io/cli/launch/bea626e2d179a083a3ba622a367e24ec ... ``` Type `y` at the prompt to open the Fly Launch page, and make the following changes to your app config: * Change the default app name and region, if needed. * For Databases, select Fly Postgres, give the Postgres database app a name (for example, your app name with `-db` appended) and choose a configuration. Once you confirm your settings, you can return to the terminal, where the launcher will: * Run the deployment setup task * Build the image * Set secrets required by (`SECRET_KEY_BASE`, for example) * Deploy the application in your selected region ```output theme={null} Waiting for launch data... Done Created app 'hello-lucky' in organization 'personal' Admin URL: https://fly.io/apps/hello-lucky Hostname: hello-lucky.fly.dev Set secrets on hello-lucky: SECRET_KEY_BASE Creating postgres cluster in organization personal Creating app... Setting secrets on app hello-lucky-db... Provisioning 1 of 1 machines with image flyio/postgres-flex:15.3@sha256:44b698752cf113110f2fa72443d7fe452b48228aafbb0d93045ef1e3282360a6 Waiting for machine to start... Machine 2865550c7e96d8 is created ==> Monitoring health checks Waiting for 2865550c7e96d8 to become healthy (started, 3/3) Postgres cluster hello-lucky-db created Username: postgres Password: EChe3BrhCjsPQEI Hostname: hello-lucky-db.internal Flycast: fdaa:2:45b:0:1::1d Proxy port: 5432 Postgres port: 5433 Connection string: postgres://postgres:EChe3BrhCjsPQEI@hello-lucky-db.flycast:5432 Save your credentials in a secure place -- you won't be able to see them again! Connect to postgres Any app within the MyOrgName organization can connect to this Postgres using the above connection string Now that you've set up Postgres, here's what you need to understand: https://fly.io/docs/postgres/getting-started/what-you-should-know/ Checking for existing attachments Registering attachment Creating database Creating user Postgres cluster hello-lucky-db is now attached to hello-lucky The following secret was added to hello-lucky: DATABASE_URL=postgres://aa_hello_elixir2:Er6pLzUBuhKcbBl@hello-lucky-db.flycast:5432/aa_hello_elixir2?sslmode=disable Postgres cluster hello-lucky-db is now attached to hello-lucky Generating rel/env.sh.eex for distributed Elixir support Preparing system for Elixir builds Installing application dependencies Running Docker release generator Wrote config file fly.toml Validating /Users/anderson/test-elixir-gs/hello_elixir2/fly.toml ✓ Configuration is valid ==> Building image Remote builder fly-builder-black-pine-7645 ready Remote builder fly-builder-black-pine-7645 ready ==> Building image with Docker --> docker host: 20.10.12 linux x86_64 ... --> Pushing image done image: registry.fly.io/hello-lucky:deployment-01HPMGHTG8XSYH3ZCV82SF5CEZ image size: 126 MB Watch your deployment at https://fly.io/apps/hello-lucky/monitoring Provisioning ips for hello-lucky Dedicated ipv6: 2a09:8280:1::2a:bc0b:0 Shared ipv4: 66.241.124.79 Add a dedicated ipv4 with: fly ips allocate-v4 Running hello-lucky release_command: /app/bin/migrate ------- ✔ release_command 784eee4c294298 completed successfully ------- This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Checking DNS configuration for hello-lucky.fly.dev Visit your newly deployed app at https://hello-lucky.fly.dev/ ``` > [!NOTE] > > Make sure to note your Postgres credentials from the output. That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * `fly logs` - Tail your application logs * `fly status` - App deployment details * `fly status -a postgres-database-app-name` - Database deployment details * `fly deploy` - Deploy the application after making changes ## *Inside `fly.toml`* The `fly.toml` file now contains a default configuration for deploying your app. In the process of creating that file, `flyctl` has also created a Fly-side application slot of the same name, "hello-lucky". If we look at the `fly.toml` file we can see the name in there: ```toml theme={null} app = "hello-lucky" kill_signal = "SIGINT" kill_timeout = 5 processes = [] [env] [experimental] allowed_public_ports = [] auto_rollback = true [[services]] http_checks = [] internal_port = 8080 processes = ["app"] protocol = "tcp" script_checks = [] [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s" ``` The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name/value at the start. That name will be used to identify the application to the Fly platform. The rest of the file contains settings to be applied to the application when it deploys. We'll have more details about these properties as we progress, but for now, it's enough to say that they mostly configure which ports the application will be visible on. ## *Deploying to Fly* To deploy changes to your app, just run just run: ```bash theme={null} flyctl deploy ``` This will lookup our `fly.toml` file, and get the app name `hello-lucky` from there. Then `flyctl` will start the process of deploying our application to the Fly platform. Flyctl will return you to the command line when it's done. Alternatively, you can use [GitHub Actions to deploy your app to Fly](/launch/continuous-deployment-with-github-actions) ## *Viewing the Deployed App* Now the application has been deployed, let's find out more about its deployment. The command `fly status` will give you all the essential details. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = hello-lucky Owner = personal Version = 0 Status = running Hostname = hello-lucky.fly.dev Deployment Status ID = 8d35eade-8c68-499e-e0a9-c6829e3c1c9d Version = v0 Status = successful Description = Deployment completed successfully Instances = 1 desired, 1 placed, 1 healthy, 0 unhealthy Instances ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED 235f9667 app 0 ord run running 1 total, 1 passing 0 1m2s ago ``` If you want to know what IP addresses the app is using, try `flyctl ips list`: ```bash cmd theme={null} flyctl ips list ``` ```out out theme={null} TYPE ADDRESS REGION CREATED AT v4 66.51.123.94 global 3m20s ago v6 2a09:8280:1::3:23f4 global 3m20s ago ``` ## *Connecting to the App* The quickest way to browse your newly deployed application is with the `flyctl apps open` command. ```bash cmd theme={null} flyctl apps open ``` ```output output theme={null} Opening https://hello-lucky.fly.dev/ ``` Your browser will be sent to the displayed URL. ## Multi-Region Deployment with Postgres (Optional) Fly also supports multi-region deployments. To deploy to multiple regions, first [Create a PostgreSQL Cluster](/unmanaged-postgres), then follow these steps: 1. Configure your primary region by setting the `PRIMARY_REGION` in your fly.toml. ```toml theme={null} [env] PRIMARY_REGION = "ord" ``` 1. Add the [superfly/fly.cr shard](https://github.com/superfly/fly.cr) to your project ```yaml theme={null} dependencies: fly: github: superfly/fly.cr version: ~> 0.1 ``` 1. Require `fly/pg/error_handler` and `fly/avram` *after* `avram` ```crystal theme={null} # src/shards.cr # ... require "avram" # ... require "fly/pg/error_handler" require "fly/avram" # ... ``` 1. Add the `Fly::PG::ErrorHandler` middleware to your list of middlewares, *after* the `Lucky::ErrorHandler` ```crystal theme={null} # src/app_server.cr # ... def middleware : Array(HTTP::Handler) [ # ... Lucky::ErrorHandler.new(action: Errors::Show), Raven::Lucky::ErrorHandler.new, Fly::PG::ErrorHandler.new, # ..,. ]of HTTP::Handler end # ... ``` 1. Deploy with `fly deploy` 2. You can see replays in the `fly log` ## *Arrived at Destination* You have successfully built, deployed, and connected to your first Crystal application on Fly. # Deploy with a Dockerfile Source: https://docs.fly.io/languages-and-frameworks/dockerfile Deploy an app from your own Dockerfile on Fly.io with fly launch, then add env vars to fly.toml, set secrets, and redeploy with fly deploy. ![Illustration by Annie Ruygt of a whale carrying containers with fish looking on](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docker.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=b170bfccc698631f3ae97bf8adc1a8e7) Fly Launch can deploy your app from a [Dockerfile](https://docs.docker.com/reference/dockerfile/). 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. ## Create and configure your app Run `fly launch` from your project's source directory. > [!NOTE] > > If you want to configure more than the basic settings, things like secrets or environment variables for example, run > `fly launch --no-deploy` so that you can [edit the `fly.toml` file](/reference/configuration) before the first deployment. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Dockerfile app Creating app in /Users/name/test/my-app-name We're about to launch your app on Fly.io. Here's what you're getting: Organization: MyOrg (fly launch defaults to the personal org) Name: my-app-name (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes ``` Type `n` if you're happy with the defaults listed, otherwise type `y` to open the Fly Launch web page and edit your settings, including: * Name: Keep the default app name or enter your own. * Region: Keep the fastest [region](/reference/regions) (the value of `primary_region` in the `fly.toml` file) as chosen by Fly Launch, or select a different region to deploy to. * Services: The port for services (the value of `internal_port` in the `fly.toml` file) depends on the `EXPOSE` instruction in your Dockerfile. The default port when the `fly launch` command doesn't find ports set in a Dockerfile is `8080`. * You can also set a default Machine size and memory, and add and configure Postgres or Redis. After you click **Confirm Settings**, Fly Launch creates your app, generates a `fly.toml` file for your project with the settings, and then deploys the app. ### Add environment variables and secrets Most Dockerfiles expect some configuration settings through `ENV`. Add your custom `ENV` settings to the `[env]` section of the generated `fly.toml` file. For example: ```toml theme={null} [env] MY_SPECIAL_ENV = "some_value" MAX_PLAYER_COUNT = "15" ``` Add the values your Dockerfile requires. Sometimes you have secrets that shouldn't be checked in to `git` or shared publicly. You can set secrets using the [`fly secrets`](/apps/secrets#set-secrets) command. For example: `bash cmd flyctl secrets set MY_SECRET=romance ` `output output Secrets are staged for the first deployment ` You can list the secrets you've already set using `fly secrets list`. For example: ```bash theme={null} fly secrets list ``` ``` NAME DIGEST DATE MY_SECRET b9e37b7b239ee4aefc75352fe3fa6dc6 1m20s ago ``` The values aren't displayed, since they're secret! ### Deploy your app If you didn't deploy the new app, or you've made changes and want to redeploy, then you can do that now. ```bash cmd theme={null} fly deploy ``` ```output output theme={null} ==> Verifying app config Validating /Users/name/test/my-app-name/fly.toml Platform: machines ✓ Configuration is valid --> Verified app config ==> Building image Remote builder fly-builder-long-glitter-7257 ready ==> Building image with Docker ... --> Pushing image done image: registry.fly.io/my-app-name:deployment-01HGEQ1Z1DPMJ4NMPNHTA09T6T image size: 141 MB Watch your deployment at https://fly.io/apps/my-app-name/monitoring --- Updating existing machines in 'my-app-name' with rolling strategy --- ✔ [1/2] Machine 918543b477de83 [app] update succeeded ✔ [2/2] Machine e28697ce6d3986 [app] update succeeded --- Visit your newly deployed app at https://my-app-name.fly.dev/ ``` By default, `fly deploy` builds the image using a remote builder. If you have Docker running locally and want to build locally, then run `fly deploy --local-only`. After the image is built, the app gets deployed. ### Open your app Run `fly apps open`, or just go to the URL specified in the output, to open your deployed app in a browser. ## Stateful apps Lots of apps have some state that they want to keep. Learn more about the options for [databases and storage](/database-storage-guides) on Fly.io. ## Grow and scale Check out some of the ways you can increase availability, capacity, and performance with Fly.io: * Follow the blueprint for [extra Machines for more resilient apps](/blueprints/resilient-apps-multiple-machines) * Read up on [App availability and resiliency](/apps/app-availability) * [Autoscale Machines based on load or custom metrics](/reference/autoscaling) * [Scale Machine CPU and RAM](/launch/scale-machine) * [Scale Machine count](/launch/scale-count) If you have questions, need help, or want to talk about what you're building, visit our [community forum](https://community.fly.io). ``` ``` # Deploy a .NET App Source: https://docs.fly.io/languages-and-frameworks/dotnet Deploy a .NET app to Fly.io. Create an ASP.NET Core web app, configure it with fly launch, and deploy it to a Fly Machine with fly deploy. In this guide we’ll learn how to deploy a [.NET](https://dotnet.microsoft.com/) application on Fly.io. ## *Generate the .NET Application* For our example, we will create a simple "hello world" application using the default web template in .NET. We assume that you have already installed [.NET](https://dotnet.microsoft.com/en-us/download). Once .NET is installed, we can create a new project by running the following command: ```bash cmd theme={null} dotnet new web --name hellodotnet ``` ```output output theme={null} The template "ASP.NET Core Empty" was created successfully. Processing post-creation actions... Restoring \hellodotnet\hellodotnet.csproj: Determining projects to restore... Restored \hellodotnet\hellodotnet.csproj (in 69 ms). Restore succeeded. ``` ## *Running the Application* In the new application folder, execute the command `dotnet run` to initiate the application: ```bash cmd theme={null} dotnet run ``` ```output output theme={null} Building... Now listening on: http://localhost:5162 Application started. Press Ctrl+C to shut down. Hosting environment: Development ``` This command will start the "hellodotnet" application, and you will be able to access it locally using the address provided in the output. Now, let's proceed to deploy this application to Fly. ## *Install Flyctl and Login* We are ready to start working with Fly.io, and that means we need `flyctl`, our CLI app for managing apps on Fly.io. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly.io](/getting-started/sign-up-sign-in). ## *Launch the app on Fly* Each Fly App needs a `fly.toml` file to tell the system how we'd like to deploy it. That file can be automatically generated with the command `flyctl launch` command. This command will also generate a Dockerfile for deployment. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Creating app in \fly\guides\hellodotnet Scanning source code Detected a .NET app ? Choose an app name (leave blank to generate one): hellodotnet ? Select Organization: demo (demo) ? Select region: ord (Chicago, Illinois (US)) App will use 'ord' region as primary Created app 'hellodotnet' in organization 'demo' Admin URL: https://fly.io/apps/hellodotnet Hostname: hellodotnet.fly.dev ? Would you like to set up a Postgresql database now? No ? Would you like to set up an Upstash Redis database now? No Wrote config file fly.toml ? Would you like to deploy now? Yes Deploying hellodotnet ... ``` That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * `fly logs` - Tail your application logs * `fly status` - App deployment details * `fly deploy` - Deploy the application after making changes ## *Inside `fly.toml`* The `fly.toml` file now contains a default configuration for deploying your app. In the process of creating that file, `flyctl` has also created a Fly-side application slot of the same name, "hellodotnet". If we look at the `fly.toml` configuration file we can see the name in there: ```toml theme={null} app = "hellodotnet" primary_region = "ord" [http_service] internal_port = 8080 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ["app"] ... ``` The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name/value at the start. That name will be used to identify the application to the Fly.io platform. The rest of the file contains settings to be applied to the application when it deploys. ## Deploying to Fly.io To deploy changes to your app, just run just run: ```bash theme={null} fly deploy ``` This will lookup our `fly.toml` file and get the app name `hellodotnet` from there. Then `flyctl` will start the process of deploying our application to Fly.io using the Dockerfile. Flyctl will return you to the command line when it's done. ## *Viewing the Deployed App* If you want to find out more about the deployment. The command `fly status` will give you all the essential details. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = hellodotnet Owner = personal Hostname = hellodotnet.fly.dev Image = hellodotnet:deployment-01H4MDR3GVFX20X5DNVZS4BPWK Platform = machines Machines PROCESS ID VERSION REGION STATE CHECKS LAST UPDATED app 91857500000083 1 ord started 2023-07-06T01:49:28Z app e2865100000086 1 ord stopped 2023-07-06T01:50:04Z ``` ## *Connecting to the App* The quickest way to browse your newly deployed application is with the `flyctl apps open` command. ```bash cmd theme={null} flyctl apps open ``` ```output output theme={null} Opening https://hellodotnet.fly.dev/ ``` Your browser will be sent to the displayed URL. ## *Bonus Points* If you want to know what IP addresses the app is using, try `fly ips list`: ```bash cmd theme={null} fly ips list ``` ```out out theme={null} VERSION IP TYPE REGION CREATED AT v6 2a09:8280:1::69:37fd public global 18m55s ago v4 66.241.125.223 public (shared) ``` ## Arrived at Destination You have successfully built, deployed, and connected to your first .NET web application on Fly.io. # Deploy a Go App Source: https://docs.fly.io/languages-and-frameworks/golang Deploy a Go app to Fly.io. fly launch detects Go, generates a Dockerfile and fly.toml for your web server, and deploys it to a Fly Machine. ![Illustration by Annie Ruygt of a seated gopher dreaming](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/golang.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=4e794257ec4a655cf5ab6d615c8204f0) In this guide we’ll learn how to deploy a Go application on Fly.io. > [!NOTE] > > Already have a Go Fly App? Try adding [Tigris](https://www.tigrisdata.com/docs/overview/) for file storage. Tigris is scalable, secure, global object storage and you can [integrate it quickly with your Go app](#add-tigris-global-storage-optional). ## *The example application* You can get the code for the example from [the GitHub repository](https://github.com/fly-apps/go-example). Just `git clone https://github.com/fly-apps/go-example` to get a local copy. The `go-example` application is, as you'd expect for an example, small. It's a Go application that uses the HTTP server and templates from the standard library. Here's all the code from `main.go`: ```golang theme={null} package main import ( "embed" "html/template" "log" "net/http" "os" ) //go:embed templates/* var resources embed.FS var t = template.Must(template.ParseFS(resources, "templates/*")) func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { data := map[string]string{ "Region": os.Getenv("FLY_REGION"), } t.ExecuteTemplate(w, "index.html.tmpl", data) }) log.Println("listening on", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } ``` The `main` function starts a server that responds with an HTML page showing the fly region that served the request. The template lives in `./templates/` and is embedded into the binary using the [embed](https://golang.org/pkg/embed/) package in go 1.16+. The template itself, `index.html.tmpl`, is very simple too: ```html theme={null}

Hello from Fly

{{ if .Region }}

I'm running in the {{.Region}} region

{{end}} ``` ## *Building the application* As with most Go applications, a simple `go build` will create a `hellofly` binary which we can run. It'll default to using port 8080 and you can view it on localhost:8080 with your browser. So, the raw application works. Now to package it up for Fly. ## *Install flyctl and log in* We are ready to start working with Fly and that means we need `flyctl`, our CLI app for managing apps on Fly. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). ## *Launch the app on Fly.io* To launch an app on fly, run `flyctl launch` in the directory with your source code. This will create and configure a fly app for you by inspecting your source code, then prompt you to deploy. ```bash cmd theme={null} flyctl launch ``` ```output output theme={null} Scanning source code WARN no go.sum file found, please adjust your Dockerfile to remove references to go.sum Detected a Go app Creating app in /path/to/your/app We're about to launch your Go app on Fly.io. Here's what you're getting: Organization: Your (fly launch defaults to the personal org) Name: hellofly (derived from your directory name) Region: Ashburn, Virginia (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? n ... Your app is ready! Deploy with `flyctl deploy` ``` First, this command scans your source code to determine how to build a deployment image as well as identify any other configuration your app needs, such as secrets and exposed ports. After your source code is scanned and the results are printed, you'll be prompted to ask if you want to change any of the defaults `flyctl` has set. Choosing yes will bring you to a page in your browser where you can change any configuration, such as the region to deploy into or the size of the VM created. Once that is complete, `flyctl` will create a `fly.toml` file containing the configuration selected. It might then begin a deployment, or let you know you should start a deployment yourself via `flyctl deploy`. ## *Inside `fly.toml`* The `fly.toml` file now contains a default configuration for deploying your app. In the process of creating that file, `flyctl` has also created a Fly-side application slot of the same name, "hellofly". If we look at the `fly.toml` file we can see the name in there: ```toml theme={null} app = 'hellofly' primary_region = 'iad' [build] [build.args] GO_VERSION = '1.21.5' [env] PORT = '8080' [http_service] internal_port = 8080 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ['app'] [[vm]] cpu_kind = 'shared' cpus = 1 memory_mb = 1024 ``` The `flyctl` command will always refer to this file in the current directory if it exists, specifically for the `app` name value at the start. That name will be used to identify the application to the Fly service. The rest of the file contains settings to be applied to the application when it deploys. You can see in the `[build]` section the builder image and the version of Go that was detected in your `go.mod` file. You can change or add build arguments to configure the build process using the `[build.args]` section. ## *Inside `Dockerfile`* [We use a Debian base image](https://github.com/superfly/flyctl/blob/master/scanner/templates/go/Dockerfile) which has `CGO` enabled by default. A minimal alternative without CGO is Alpine: ```diff theme={null} # ... - FROM golang:${GO_VERSION}-bookworm as builder + FROM golang:${GO_VERSION}-alpine as builder # ... - FROM debian:bookworm + FROM alpine:latest ``` If you want to use Alpine with `CGO` enabled, [read here](https://megamorf.gitlab.io/2019/09/08/alpine-go-builds-with-cgo-enabled/). ## *Deploying to Fly.io* To deploy your app, just run: ```bash theme={null} flyctl deploy ``` This will lookup our `fly.toml` file, and get the app name `hellofly` from there. Then `flyctl` will start the process of deploying our application to the Fly platform. Flyctl will return you to the command line when it's done. ## *Viewing the deployed app* Now the application has been deployed, let's find out more about its deployment. The command `flyctl status` will give you all the essential details. ```bash cmd theme={null} flyctl status ``` ```output output theme={null} App Name = hellofly Owner = demo Hostname = hellofly.fly.dev Image = hellofly:deployment-abcdefghxyz Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 0ac9ed79 1 iad running 2024-02-13T12:52:38Z app 1bd10fe8 1 iad stopped 2024-02-13T12:52:38Z $ ``` As you can see, the application has been deployed with a DNS hostname of hellofly.fly.dev, and an instance is running in Virginia. Your deployment's name will, of course, be different. ## *Connecting to the app* The quickest way to browse your newly deployed application is with the `flyctl apps open` command. ```bash cmd theme={null} flyctl apps open ``` ```output output theme={null} opening https://golang-example.fly.dev ... ``` Your browser will be sent to the displayed URL. ## *Add Tigris Global Storage (optional)* Tigris provides CDN-like accessibility for your stored objects. Tigris has S3-API compatibility, automatically manages data replication and caching for you, and offers AuthN and AuthZ authentication mechanisms. Learn more about [Tigris Object Storage](https://www.tigrisdata.com/docs/overview/). 1. **Create a Tigris bucket**: run the create command inside your Go Fly App's directory. Afterwards, make sure to take note of the credentials received: ```bash theme={null} fly storage create ``` 2. **Retrieve AWS SDK modules**: Tigris has an S3 compatible API service, thus, we can easily use [AWS SDK Go](https://aws.github.io/aws-sdk-go-v2/docs/getting-started/#install-the-aws-sdk-for-go-v2) modules to connect with the Tigris Bucket we've just created. ```bash theme={null} go get github.com/aws/aws-sdk-go-v2 go get github.com/aws/aws-sdk-go-v2/config go get github.com/aws/aws-sdk-go-v2/service/s3 ``` 3. **Create a [Client function](https://github.com/fly-apps/go-example-tigris/blob/main/tigris/tigris.go#L12)** that will return an instance ready to connect with a Tigris bucket: ```go theme={null} func Client(ctx context.Context) (*s3.Client, error) { // 1. Create an aws.Config instance cfg, err := config.LoadDefaultConfig(ctx) if err != nil { return nil, fmt.Errorf("failed to load Tigris config: %w", err) } // 2. Create an Amazon S3 client using the AWS Config instance created, "cfg" return s3.NewFromConfig(cfg, func(o *s3.Options){ o.BaseEndpoint = aws.String("https://fly.storage.tigris.dev") o.Region = "auto" }), nil } ``` The `LoadDefaultConfig()` function will create an [aws.Config instance](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#Config) that can load [credentials from environment variables](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials). Passing this instance to the `NewFromConfig()` [function](https://aws.github.io/aws-sdk-go-v2/docs/making-requests/#newfromconfig) creates an S3 service client we've configured to connect with the Tigris endpoint. 4. **Set up Tigris Credentials**. To successfully connect with our Tigris Bucket, we can set our Tigris credentials as AWS SDK environment variables. **In a local setup**, this should look something like: ```bash theme={null} export AWS_ACCESS_KEY_ID=TIGRIS_ACCESS_KEY_ID export AWS_SECRET_ACCESS_KEY=TIGRIS_SECRET_ACCESS_KEY ``` **For our Go Fly App**, these environment variables should have already been set as [secrets](/flyctl/cmd/fly_secrets) during the running of `fly storage create`: ```output theme={null} Setting the following secrets on ...: AWS_ACCESS_KEY_ID: AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev AWS_REGION: auto AWS_SECRET_ACCESS_KEY: BUCKET_NAME: ``` Of course, this would only automatically happen if the create command was run in a directory containing a `fly.toml` for our Go Fly app. But if that wasn't the case, we can still manually set necessary [secrets](/flyctl/cmd/fly_secrets) like so: ```bash theme={null} fly secrets set AWS_ACCESS_KEY_ID="" fly secrets set AWS_SECRET_ACCESS_KEY="" ``` 5. **[Upload a file](https://github.com/fly-apps/go-example-tigris/blob/main/tigris/tigris.go#L26):** Aside from connecting with a Bucket, we can use other [AWS SDK functions](https://docs.aws.amazon.com/code-library/latest/ug/go_2_s3_code_examples.html). Let's use the `PutObject` function to upload a text file on the go like so: ```go theme={null} func Client( ctx context.Context ) (*s3.Client, error){} func UploadText( ctx context.Context ) (string, error){ // 1. Create connection via client conn, err := Client( ctx ) // 2. Upload sample file if err==nil{ _, err = conn.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String(""), Key: aws.String("sample.txt"), Body: strings.NewReader("testing content"), }) } if err!=nil{ return err.Error(), err }else{ return "success",nil } } ``` For further reference, please see official Tigris [documentation for AWS SDK GO](https://www.tigrisdata.com/docs/sdks/s3/aws-go-sdk/), as well as [this compact package](https://github.com/Xe/x/blob/master/tigris/tigris.go) containing helpers for interacting with Tigris. ## *Bonus points* If you want to know what IP addresses the app is using, try `flyctl ips list`: ```bash cmd theme={null} flyctl ips list ``` ```out out theme={null} TYPE ADDRESS CREATED AT v4 50.31.246.73 23m42s ago v6 2a09:8280:1:3949:7ac8:fe55:d8ad:6b6f 23m42s ago ``` ## *Arrived at destination* You have successfully built, deployed, and connected to your first Go application on Fly. # Language & Framework Guides Source: https://docs.fly.io/languages-and-frameworks/index Every language guide for Fly.io: in-depth docs for Elixir, Rails, Laravel, Django, JS, Rust and Python, plus starters for Go, .NET, Crystal and WordPress. ![Illustration by Annie Ruygt of different programming frameworks mascots floating together](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/frameworks.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=5edcdf4634974359c112fbe07f90b18d) Fly.io can run pretty much any type of application. If you don't see it listed here, it's a matter of finding and configuring a Dockerfile that runs it. ## Comprehensive Guides Fly.io supports all languages and frameworks, but you'll notice some have much more extensive documentation and support. That's because we have people at Fly.io who work full time on documenting and nurturing these frameworks. * [Elixir on Fly.io](/elixir/) * [Rails on Fly.io](/rails/) * [Laravel on Fly.io](/laravel/) * [Django on Fly.io](/django/) * [JavaScript on Fly.io](/js/) * [Rust on Fly.io](/rust/) * [Python on Fly.io](/python/) ## Starter Guides The following frameworks & languages are fully supported by Fly.io. Each one of the language guides will take you through creating and deploying a simple application. * [Deploy a Go App](/languages-and-frameworks/golang/) * [Deploy a Static Website](/languages-and-frameworks/static/) * [Deploy a Ruby App](/languages-and-frameworks/ruby/) * [Deploy with a Dockerfile](/languages-and-frameworks/dockerfile/) * [Deploy a Crystal App](/languages-and-frameworks/crystal/) * [Deploy a .NET App](/languages-and-frameworks/dotnet/) * [Deploy WordPress](/languages-and-frameworks/wordpress/) If you don't see a framework supported here that you'd like to run on Fly.io, [tell us in the Community](https://community.fly.io/). # Deploy a Ruby App Source: https://docs.fly.io/languages-and-frameworks/ruby Deploy a Ruby app to Fly.io. Any Rack app with a Gemfile and config.ru, like Sinatra, can be launched with fly launch and shipped with fly deploy. Deploying a Ruby application on Fly.io requires two essential ingredients: a [Gemfile](/languages-and-frameworks/ruby) and a [config.ru](https://github.com/rack/rack/wiki/\(tutorial\)-rackup-howto) file. Full-stack frameworks like Rails, Padrino, and Hanami provide both of these for you. Micro-frameworks like Sinatra, Camping, and Rack itself require you to create your own. ## Gemfile If you don't have a gemfile, the best way to get started is to run: ```bash theme={null} bundle init ``` Once this file is created, proceed to add gems needed by your application, for example: ```bash theme={null} bundle add rackup ``` The Gemfile needs to include the framework you may be using, so if you are using, for example, Sinatra or Camping, add that gem too. Finally add a web server, like puma, thin, or falcon. If you are not sure what web server to use, puma is a safe bet: ```bash theme={null} bundle add puma ``` ## config.ru The most basic `config.ru` file would be the following: ```ruby theme={null} run do |env| [200, {}, ["Hello World"]] end ``` Phusion Passenger guides provide a [helpful list](https://www.phusionpassenger.com/docs/advanced_guides/deployment_and_scaling/apache/config_ru.html) of starter `config.ru` files for various Ruby web frameworks. We're now ready to run the app locally. ## *Running The Application* Run `bundle exec rackup` to start the application ```bash cmd theme={null} bundle exec rackup ``` ```output output theme={null} Puma starting in single mode... * Puma version: 6.1.1 (ruby 3.2.1-p31) ("The Way Up") * Min threads: 0 * Max threads: 5 * Environment: development * PID: 26873 * Listening on http://127.0.0.1:9292 * Listening on http://[::1]:9292 Use Ctrl-C to stop ``` If you see a message suggesting that you need to run `bundle binstub rack`, that message can be safely ignored. And connect to localhost:9292 to confirm that you have a working Ruby application. Now to package it up for Fly. ## *Install Flyctl and Login* We are ready to start working with Fly and that means we need `flyctl`, our CLI app for managing apps on Fly. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). ## *Launch the app on Fly* To launch an app on fly, run `flyctl launch` in the directory with your source code. This will create and configure a fly app for you by inspecting your source code, then prompt you to deploy. ```bash cmd theme={null} flyctl launch ``` ```output output theme={null} Creating app in /Users/rubys/tmp/snapshot Scanning source code Detected a Ruby app ? Choose an app name (leave blank to generate one): ? Select Organization: Sam Ruby (personal) Some regions require a paid plan (fra, maa). See https://fly.io/plans to set up a plan. ? Choose a region for deployment: Ashburn, Virginia (US) (iad) Created app 'late-bird-1771' in organization 'personal' Admin URL: https://fly.io/apps/late-bird-1771 Hostname: late-bird-1771.fly.dev ? Would you like to set up a Postgresql database now? No ? Would you like to set up an Upstash Redis database now? No Wrote config file fly.toml Your Ruby app is prepared for deployment. If you need custom packages installed, or have problems with your deployment build, you may need to edit the Dockerfile for app-specific changes. If you need help, please post on https://community.fly.io. Now: run 'fly deploy' to deploy your Ruby app. ... ``` First, this command scans your source code to determine if it meets the requirements for a deployable Ruby app. After your source code is scanned and the results are printed, you'll be prompted for an organization. Organizations are a way of sharing application and resources between Fly users. Every fly account has a personal organization, called `personal`, which is only visible to your account. Let's select that for this guide. Next, you'll be prompted to select a region to deploy in. The closest region to you is selected by default. You can use this or change to another region. Finally you will be given the option to start a Postgres and/or a Redis database. If you need one or both, it is convenient to set them up now. Otherwise you can always add them later. At this point, `flyctl` creates an app for you and writes your configuration to a `fly.toml` file. You'll then be prompted to build and deploy your app. Once complete, your app will be running on fly. ## *Config First!* Most Dockerfiles expect some configuration settings through `ENV`. The generated `fly.toml` file has a place for you to add your custom `ENV` settings. It's the `[env]` block. ```toml theme={null} [env] MY_SPECIAL_ENV = "some_value" MAX_PLAYER_COUNT = "15" ``` Add whatever values your Dockerfile or container requires. Sometimes you have secrets that shouldn't be checked in to `git` or shared publicly. For those settings, you can set them using [`fly secrets`](/apps/secrets#set-secrets). ```bash cmd theme={null} fly secrets set MY_SECRET=romance ``` ```output output theme={null} Secrets are staged for the first deployment ``` You can list the secrets you've already set using `fly secrets list` ```bash theme={null} fly secrets list ``` ``` NAME DIGEST DATE MY_SECRET b9e37b7b239ee4aefc75352fe3fa6dc6 1m20s ago ``` The values aren't displayed since they are secret! ## *Deploying to Fly* To deploy changes to your app, just run: ```bash theme={null} fly deploy ``` This will lookup our `fly.toml` file, and get the app name from there. Then `fly` will start the process of deploying our application to the Fly platform. Flyctl will return you to the command line when it's done. ## *Connecting to the App* The quickest way to browse your newly deployed application is with the `flyctl apps open` command. ```bash cmd theme={null} fly apps open ``` ```output output theme={null} Opening https://helloruby.fly.dev/ ``` Your browser will be sent to the displayed URL. ## *Arrived at Destination* You have successfully built, deployed, and connected to your first Ruby application on Fly. You can monitor and explore your app with the following command: ```bash theme={null} fly dashboard ``` # Deploy a Static Website Source: https://docs.fly.io/languages-and-frameworks/static Deploy and host a static website on Fly.io. Serve your HTML, CSS, and JavaScript with nginx in a small Docker image and deploy it with flyctl. ![Illustration by Annie Ruygt of a flag depicting a Hello World app](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/static.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=1b672fb159c557394d6d0b74ce0e36a5) In this guide we’ll learn how to deploy a static site on Fly.io. 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. You can clone all the files needed for this example from [the hello-static GitHub repository](https://github.com/fly-apps/hello-static) to a local directory: ```bash theme={null} git clone https://github.com/fly-apps/hello-static ``` Alternatively, you can create all the files manually as you work through this guide. ## *Install flyctl and login* To configure our application and deploy it on Fly.io, we need `flyctl`, our CLI app for managing apps on Fly. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). ## *Putting the app together* At this point, if you have a local clone of the `hello-static` repository, you could go ahead and run `fly launch` from its root directory and get the static site deployed without further ado. But that wouldn't be very illuminating. Let's go through what's included in the example repository and why. If you cloned the repository, your new app already has its own directory. Otherwise, create one. This isn't just for tidiness, or for letting `flyctl` detect your app by the `fly.toml` in the working directory (although these are good reasons). It also ensures that no extra files get included in the [build context](https://docs.docker.com/engine/reference/commandline/build/) when the Docker image gets built. We'll do everything from within this directory: ```bash theme={null} cd hello-static ``` ### *The Site* Our example will be a simple static site. That can be as trivial as a single `index.html` file. Let's make it only slightly more complicated by writing two HTML files and having them link to each other. Put these HTML files into a subdirectory of their own, called `public`. Files in this directory are the ones our nginx server will serve. Create the `hello-static/public` directory if needed. Here's `index.html`, which is the landing page: ```html theme={null} Hello from Fly

Hello from Fly with a static web site

Or goodbye.

``` Here's `goodbye.html`. ```html theme={null} Still Hello from Fly

You say goodbye

But I say hello.

``` ### *The Dockerfile* nginx can be run in a container, and the official nginx image is available on [Docker Hub](https://hub.docker.com/_/nginx/). This is super convenient for us, because Fly apps use container images! We can use the nginx image as a base image. We just have to copy our site's files to `/usr/share/nginx/html` in the image. Here's our Dockerfile to do that: ```docker theme={null} FROM nginx:alpine COPY public /usr/share/nginx/html ``` The Dockerfile should be placed in the working directory (here, `hello-static`). ### *Configuring the App for Fly.io* We set the initial configuration for the app by running `flyctl launch`. This takes care of setting the app name, the Fly.io organization it belongs to, and a region to deploy to. It also generates a `fly.toml` file with more configuration settings. The `hello-static` repository contains a `fly.toml` that will be detected by `flyctl launch`; you can use it to configure your app. Otherwise, a new `fly.toml` will be generated with `flyctl launch`, and we can edit it. ```bash cmd theme={null} flyctl launch ``` ```output output theme={null} Scanning source code Detected a Dockerfile app Creating app in /Users/jphenow/workspace/hello-static We're about to launch your app on Fly.io. Here's what you're getting: Organization: Jon Phenow (fly launch defaults to the personal org) Name: hello-static (generated) Region: Denver, Colorado (US) (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: (not requested) Tigris: (not requested) ? Do you want to tweak these settings before proceeding? No Created app 'hello-static' in organization 'personal' Admin URL: https://fly.io/apps/hello-static Hostname: hello-static.fly.dev Wrote config file fly.toml Validating /Users/jphenow/workspace/hello-static/fly.toml ✓ Configuration is valid ==> Building image Remote builder fly-builder-little-haze-7293 ready Remote builder fly-builder-little-haze-7293 ready ==> Building image with Docker --> docker host: 24.0.7 linux x86_64 [+] Building 0.4s (7/7) FINISHED => [internal] load .dockerignore 0.2s => => transferring context: 100B 0.2s => [internal] load build definition from Dockerfile 0.2s => => transferring dockerfile: 90B 0.2s => [internal] load metadata for docker.io/library/nginx:alpine 0.1s => [internal] load build context 0.1s => => transferring context: 151B 0.1s => [1/2] FROM docker.io/library/nginx:alpine@sha256:a5127daff3d6f4606be3100a252419bfa84fd6ee5cd74d0feaca1a5068f97dcf 0.0s => CACHED [2/2] COPY public /usr/share/nginx/html 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:1e96a09c0ae89bb9b8dc5dcdcfde700fb1ee2677c20dc9d5f324a5b768328dda 0.0s => => naming to registry.fly.io/hello-static:deployment-01J8071W7G3S6R2MC5JJX915CC 0.0s --> Building image done ==> Pushing image to fly The push refers to repository [registry.fly.io/hello-static] deployment-01J8071W7G3S6R2MC5JJX915CC: digest: sha256:2ba357ed3fcf6eeb3a5e4c7a6e2300040cf6f414db69ce2e3b7d6030507deb5e size: 2196 --> Pushing image done image: registry.fly.io/hello-static:deployment-01J8071W7G3S6R2MC5JJX915CC image size: 43 MB Watch your deployment at https://fly.io/apps/hello-static/monitoring Provisioning ips for hello-static Dedicated ipv6: 2a09:8280:1::46:a909:0 Shared ipv4: 66.241.124.149 Add a dedicated ipv4 with: fly ips allocate-v4 This deployment will: * create 2 "app" machines No machines in group app, launching a new machine WARNING The app is not listening on the expected address and will not be reachable by fly-proxy. You can fix this by configuring your app to listen on the following addresses: - 0.0.0.0:8080 Found these processes inside the machine with open listening sockets: PROCESS | ADDRESSES ---------------------------------------------*---------------------------------------- nginx: master process nginx -g daemon off; | 0.0.0.0:80, [::]:80 /.fly/hallpass | [fdaa:0:7300:a7b:d827:5e53:ee2d:2]:22 nginx: worker process | 0.0.0.0:80, [::]:80 Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = "stop"' that will be stopped when idling ------- Checking DNS configuration for hello-static.fly.dev Visit your newly deployed app at https://hello-static.fly.dev/ ``` This has configured the app with some default parameters, generated a `fly.toml`, attached a shared IPv4, created a dedicated IPv6, and deployed our image file for us. Before the app will work though, we need to do one more thing. nginx listens on port 80 by default, but the default `fly.toml` assumes port 8080. Edit `internal_port` in the [`services`](/reference/configuration#the-http_service-section) section to reflect this: ```toml theme={null} [http_service] internal_port = 80 force_https = true auto_stop_machines = 'stop' auto_start_machines = true min_machines_running = 0 processes = ['app'] ``` Now we're ready to deploy: ```bash theme={null} flyctl deploy ``` The output should end something like this, if everything has gone well: ``` ==> Verifying app config Validating /Users/jphenow/workspace/hello-static/fly.toml ✓ Configuration is valid --> Verified app config ==> Building image Remote builder fly-builder-little-haze-7293 ready Remote builder fly-builder-little-haze-7293 ready ==> Building image with Docker --> docker host: 24.0.7 linux x86_64 [+] Building 0.4s (7/7) FINISHED => [internal] load build definition from Dockerfile 0.2s => => transferring dockerfile: 90B 0.2s => [internal] load .dockerignore 0.2s => => transferring context: 100B 0.1s => [internal] load metadata for docker.io/library/nginx:alpine 0.1s => [internal] load build context 0.1s => => transferring context: 151B 0.1s => [1/2] FROM docker.io/library/nginx:alpine@sha256:a5127daff3d6f4606be3100a252419bfa84fd6ee5cd74d0feaca1a5068f97dcf 0.0s => CACHED [2/2] COPY public /usr/share/nginx/html 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:1e96a09c0ae89bb9b8dc5dcdcfde700fb1ee2677c20dc9d5f324a5b768328dda 0.0s => => naming to registry.fly.io/hello-static:deployment-01J807A99M034C3P47SBXFZZVZ 0.0s --> Building image done ==> Pushing image to fly The push refers to repository [registry.fly.io/hello-static] 052eb83bbfc7: Layer already exists b0f60355fd52: Layer already exists 027907faf592: Layer already exists 11134cc97d7f: Layer already exists f7a5847cdca9: Layer already exists aec1e8cf14f5: Layer already exists 717b3a077b07: Layer already exists 2ff96b2e5450: Layer already exists 63ca1fbb43ae: Layer already exists deployment-01J807A99M034C3P47SBXFZZVZ: digest: sha256:2ba357ed3fcf6eeb3a5e4c7a6e2300040cf6f414db69ce2e3b7d6030507deb5e size: 2196 --> Pushing image done image: registry.fly.io/hello-static:deployment-01J807A99M034C3P47SBXFZZVZ image size: 43 MB Watch your deployment at https://fly.io/apps/hello-static/monitoring ------- Updating existing machines in 'hello-static' with rolling strategy ------- ✔ [1/2] Cleared lease for 3287eedf034558 ✔ [2/2] Cleared lease for 4d89447f4117d8 ------- Checking DNS configuration for hello-static.fly.dev Visit your newly deployed app at https://hello-static.fly.dev/ ``` ## *Viewing your static site* The quickest way to browse your newly deployed application is with the `flyctl apps open` command. ```bash cmd theme={null} flyctl apps open ``` ```output output theme={null} opening https://hello-static.fly.dev ... ``` Your browser will be sent to the displayed URL. # Deploy WordPress Source: https://docs.fly.io/languages-and-frameworks/wordpress Deploy WordPress to Fly.io using the official WordPress Docker image and a MySQL database, configured and launched with fly launch. Setting up a WordPress application on Fly.io is quite simple. In this guide, we'll learn just how simple it is! There's one thing you need before setting up a WordPress application: a MySQL database. You can find out how to get one set up on Fly.io in [this handy guide](/app-guides/mysql-on-fly). Make sure you remember the hostname (this will be `.internal`), the database name and the database credentials. Ready? Let's get started! ## Launch a new WordPress application To launch a new WordPress application, we can use the [official WordPress docker image](https://hub.docker.com/_/wordpress). To do so, run the following command: ```bash cmd theme={null} flyctl launch -i wordpress --no-deploy ``` ```output output theme={null} Using image wordpress Creating app in /Users/name/test/my-app-name We're about to launch your app on Fly.io. Here's what you're getting: Organization: MyOrg (fly launch defaults to the personal org) Name: my-app-name (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes ``` Type `n` if you're happy with the defaults listed, otherwise type `y` to open the Fly Launch web page and edit your settings, including: * Name: Keep the default app name or enter your own. This name will be part of the URL for your app, unless you use a custom domain. * Region: Keep the fastest [region](/reference/regions) (the value of `primary_region` in the `fly.toml` file) as chosen by Fly Launch, or select a different region to deploy to. After you click **Confirm Settings**, Fly Launch creates your App and generates a `fly.toml` file for your project with the settings. The reason we added the `--no-deploy` flag is so that the App doesn't get deployed right away. This way, we can set up things like secrets and environment variables before the first deployment. ## Configure & deploy the WordPress application Before deploying, we'll need to configure a couple of things. In your newly created fly.toml file, change the `internal_port` under `[http_service]` from 8080 to **80**. This setting lets Fly know on what port your App will respond, which is 80 in our case. After that, set up these environment variables by adding this to the end of your `fly.toml`: ```toml theme={null} [env] WORDPRESS_DB_NAME= "wordpress_db" WORDPRESS_DB_HOST= ".internal" WORDPRESS_CONFIG_EXTRA = "define( 'WP_HOME', 'https://.fly.dev' );define( 'WP_SITEURL', 'https://.fly.dev');" ``` The `WORDPRESS_DB_NAME` and `WORDPRESS_DB_HOST` are needed for the WordPress application to connect to the database. The `WORDPRESS_CONFIG_EXTRA` makes sure that the application serves everything from the `https://...` address. Here's how the total fly.toml should look now: ```toml theme={null} app = 'fly-wordpress' primary_region = 'fra' [build] image = 'wordpress' [http_service] internal_port = 80 # changed from 8080 to 80 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ['app'] [[vm]] memory = '1gb' cpu_kind = 'shared' cpus = 1 # Add these environment variables [env] WORDPRESS_DB_NAME= "wordpress_db" WORDPRESS_DB_HOST= "db-wordpress.internal" WORDPRESS_CONFIG_EXTRA = "define( 'WP_HOME', 'https://fly-wordpress.fly.dev' );define( 'WP_SITEURL', 'https://fly-wordpress.fly.dev');" ``` Next, we need to set up the `WORDPRESS_DB_USER` and `WORDPRESS_DB_PASSWORD` variables. Since these are sensitive, let's use the [`fly secrets`](/apps/secrets#set-secrets) command: ```bash cmd theme={null} flyctl secrets set WORDPRESS_DB_USER=wordpress_user WORDPRESS_DB_PASSWORD=wordpress_password ``` ```output output theme={null} Secrets are staged for the first deployment ``` You can list the secrets you've already set using `fly secrets list`. For example: ```bash theme={null} fly secrets list ``` ``` NAME DIGEST CREATED AT WORDPRESS_DB_PASSWORD 92d2791e3f7ebd71 1m20s ago WORDPRESS_DB_USER a2b707ef6e858717 1m20s ago ``` The values aren't displayed, since they're secret! ### Deploy your app The last step is to deploy the App with the deploy command: ```bash cmd theme={null} flyctl deploy ``` ```output output theme={null} ==> Verifying app config Validating /Users/name/test/my-app-name/fly.toml ✓ Configuration is valid --> Verified app config ==> Building image Searching for image 'wordpress' remotely... image found: img_rlw04nnx6zzj4z2y Watch your deployment at https://fly.io/apps/my-app-name/monitoring ------- Updating existing machines in 'my-app-name' with rolling strategy ------- ✔ [1/2] Machine 918543b477de83 [app] update succeeded ✔ [2/2] Machine e28697ce6d3986 [app] update succeeded ------- Visit your newly deployed app at https://my-app-name.fly.dev/ ``` ### Set up a custom domain You probably want to set up a custom domain for your WordPress application. You can find all you need to know (and more!) in [this handy guide](/networking/custom-domain). ### Open your app Run `fly apps open`, or just go to the URL specified in the output of the deploy command, to open your deployed App in a browser. If you have any questions, need help, or want to talk about what you're building, visit our [community forum](https://community.fly.io). # Laravel: Advanced Guides Source: https://docs.fly.io/laravel/advanced-guides Advanced Laravel on Fly.io: multiple Fly apps for Redis and other services, LiteFS for SQLite, GitHub Actions CI/CD, Symfony, Inertia SSR, Dockerfiles. As your Laravel application becomes more popular, it will inevitably grow in complexity, the number of people using it will increase, and pretty much everything else goes up and to the right. Fortunately Fly can reduce some of that complexity by making global deployments easy and provide some best practices for scaling a Laravel application on Fly. ## Multiple Fly Applications At some point a Laravel application will need to run additional services, perhaps Redis for a cache or session storage. Learn how to manage multiple applications from one Laravel project to keep your monolith a monolith. ## Laravel Multi-SQLite: LiteFS Go LiteFS-global with your SQLite-backed Laravel Fly App! ## Laravel GitHub Actions: CICD Configure continuous integration and development on your Laravel Fly App through GitHub. ## Using Symfony See how to run Symfony apps, with just a few tweaks to the Laravel configuration. ## Using Inertia SSR See how to run Inertia SSR Laravel Apps, with just a few tweaks to the Laravel configuration. ## Customizing the Dockerfile Customize the Dockerfile to add additional modules and more # Customizing the Dockerfile Source: https://docs.fly.io/laravel/advanced-guides/customizing-dockerfile Customize the Laravel Dockerfile that fly launch generates on Fly.io, from skipping the static asset build to installing SQL Server PHP extensions before COPY. When you run `fly launch`, we create a `Dockerfile` for you. You may need to customize this for your application! The `Dockerfile` we create has three main responsibilities: 1. Copy the codebase into the container 2. Configure services (nginx, maybe configure Octane), run `composer install` and similar 3. Build static assets (via NodeJS) The `Dockerfile` assumes you'll want to run `composer install...` and build static assets *on deployment*. ## Customizing Static Assets If you have static assets already built for production (perhaps committed to Git), you can save a bunch of deployment time by cutting out the step to build static assets. You can just delete the whole section from the `Dockerfile` that deals with that - [here's a gist showing the diff](https://gist.github.com/fideloper/e393ca245b895f512944c16dce042a1e/revisions). ## Installing Extra Stuff You may also want to install additional stuff! Here's an example of installing `pecl` and pecl-specific modules `sqlsrv` and `pdo_sqlsrv`. Don't tell anyone I said this, but SqlServer is actually a great database, iykyk. We'll borrow from Microsoft's [docs here](https://learn.microsoft.com/en-us/sql/connect/php/installation-tutorial-linux-mac?view=sql-server-ver16). The trick here is knowing WHERE in the `Dockerfile` to install things. Generally, you want extra work to be done BEFORE we `COPY` our code base into the container. This is because changes to our code base will bust Docker's build cache, and all the work we do installing stuff will need to be re-done. If we install things **BEFORE** the `COPY` instruction, all that work can get cached and won't need to be re-done on each deployment! So, here's a section of the `Dockerfile` that shows installing the needed things to get SqlServer working: ```Dockerfile theme={null} # Things above this omitted LABEL fly_launch_runtime="laravel" ### ## NEW STUFF HERE # ENV ACCEPT_EULA=Y # Note we specify version 18 of SqlServer via package msodbcsql18 RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \ && curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update \ && apt-get install --no-install-recommends -y php${PHP_VERSION}-dev php-pear \ g++ make msodbcsql18 unixodbc-dev mssql-tools \ && pecl install sqlsrv \ && pecl install pdo_sqlsrv \ && printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/${PHP_VERSION}/mods-available/sqlsrv.ini \ && printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/${PHP_VERSION}/mods-available/pdo_sqlsrv.ini \ && phpenmod -v ${PHP_VERSION} sqlsrv pdo_sqlsrv ### ## END NEW STUFF # # copy application code, skipping files based on .dockerignore COPY . /var/www/html # Things below this omitted ``` Here's a [diff](https://gist.github.com/fideloper/92b6c7c89613203f45e26f70cbb161e9/revisions) for full context. We installed a bunch of stuff to get SqlServer drivers installed, and we did it above the line that `COPY`'s our code into the container! Note that we used one `RUN` command and just ran each command in sequence. This helps reduce the number of instructions in the `Dockerfile` (and therefore the number of "layers" that make up the Docker image), thus reducing the overall size of the image we deploy onto Fly. Since we put the installation of our stuff up high above the `COPY` instruction, this install step should be cached in the Docker builder cache, and shouldn't normally need to get re-run on every subsequent deployment. # Laravel GitHub Actions: CICD Source: https://docs.fly.io/laravel/advanced-guides/github-actions Auto-deploy a Laravel app to Fly.io from GitHub Actions: launch the app, push it to a GitHub repo, and add a deploy token and the flyctl-actions workflow. Laravel applications are alive and bustling with feature updates and revisions. You'll want to add your Laravel Fly App in a GitHub repository for safe-versions-keeping, healthy-code-reviews, and good-old team-code management. Once you've set up a repository for your Fly App, you can automate your team's deployment workflow with [GitHub Actions](https://docs.github.com/en/actions)---a really neat way to automate most things you'd manually do during and after code reviews! ## Initialization In this section, you'll create a new Laravel application, configure and deploy it as a Fly App, and finally add it to a GitHub repository. 1. Start with a [clean slate](/laravel): create a new Laravel project and configure it as a deployable Fly app by using the `flyctl` launch command: ```bash theme={null} # Initialize your Laravel project composer create-project laravel/laravel fly-laravel cd fly-laravel # Auto generate your `fly.toml` configuration file: # Add in the ams region with --region # Auto deploy using --now while your at it! flyctl launch --region ams --now ``` 2. Create a new github repository for your current Laravel Fly App. You can create one either through: 1. **GitHub Console** - Visiting your [create a new repository](https://github.com/new) page in your console 2. **GitHub CLI** - run the create repo command: ```bash theme={null} gh repo create --public ``` 3. Finally, initialize your Laravel Fly App's directory as part of a git repository, and point its remote to the recently created repository above ```bash theme={null} git init git remote add origin git@github.com:/.git ``` ## GitHub CI Action: Auto Deploy to Fly.io Once you have your Laravel application set up with a github repository, you can configure some GitHub Actions to auto deploy your changes. In this guide, you'll be using `Fly.io`'s very own [GitHub action template here](https://github.com/superfly/flyctl-actions) to help deploy your application. 1. Generate a `Fly token` with `fly tokens deploy` 2. Then save your newly generated `FLY_API_TOKEN` in your github repository either through the GitHub Console or GitHub CLI: 1. **GitHub Console** - Open your repository's Settings tab, then add a secret named `FLY_API_TOKEN` under the Secrets section. 2. **GitHub CLI** - run the command below ```bash cmd theme={null} gh secret set FLY_API_TOKEN --repos / ``` ```output output theme={null} ? Paste your secret ******************************************* ✓ Set Actions secret FLY_API_TOKEN for / ``` 3. Once you have your `FLY_API_TOKEN` safely stored as a secret in your github repository, proceed with creating a GitHub workflow yml file in `.github/workflows/main.yml`: ```bash theme={null} mkdir -p .github/workflows touch .github/workflows/main.yml ``` ```yml theme={null} name: Fly Deploy on: [push] env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} jobs: deploy: name: Deploy app runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy ``` Let's go through each line above, shall we? * **name: Fly Deploy** => This is the Workflow name * **on: \[push]** => Any push action to the repository triggers the current workflow * **env: \`FLY\_API\_TOKEN\`** => Environment variable set for the GitHub Actions container * **jobs:** => This is the list of jobs to run for the workflow * **name: Deploy app** => Each job would have a name * **runs-on: ubuntu-latest** => Each job would have an container environment we want to run its virtual machine on * **steps:** => Each job would have a list of steps, see below: * **uses: actions/checkout\@v2** => This is a pre-made GitHub Action. It allows checking out of your repository into the virtual machine spun up for the job * **uses: superfly/flyctl-actions/setup-flyctl\@master** => This is one of Fly.io's pre-made GitHub action. It sets up \`flyctl\` in the virtual machine spun up for the job * **run: flyctl deploy** => Need I say more? 4. Save and push your changes with: ```bash theme={null} git add . git commit -m "Configure auto-deploy through GitHub Actions" git push ``` 5. Visit the Actions tab of your repository and see how your deployment fares! # Laravel Multi-SQLite: LiteFS Source: https://docs.fly.io/laravel/advanced-guides/global-sqlite-litefs Replicate a Laravel app's SQLite database across Fly.io Machines with LiteFS, including litefs.yml and FUSE setup and fly-replay middleware for writes. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. This is a technology preview. ["LiteFS"](/litefs) is a `distributed file system` that enables you to sync SQLite data across instances of your application. Read further on the official [LiteFS documentation](/litefs/getting-started-fly) and follow along a LiteFS-configured [sample Laravel repository here](https://github.com/fly-apps/fly-laravel-litefs). ## Laravel Specific Configuration Take note of the following important steps in configuring your [Laravel Fly App](/laravel) with `LiteFS`: 1. You'll have to configure SQLite connection in your [`fly.toml`](https://github.com/fly-apps/fly-laravel-litefs/blob/main/fly.toml) file: ```toml theme={null} [env] ... DB_CONNECTION="sqlite" DB_DATABASE="/var/www/html/storage/database/database.sqlite" ``` 2. Since we're saving the SQLite database in the storage directory, make sure to mount a volume on it by following this [guide here](/laravel/the-basics/laravel-volume-storage). 3. Execute `litefs mount` instead of starting the server in [`.fly/entrypoint.sh`](https://github.com/fly-apps/fly-laravel-litefs/blob/main/.fly/entrypoint.sh): ``` exec litefs mount ``` 4. Make sure to properly configure the configuration file( [etc/litefs.yml](https://github.com/fly-apps/fly-laravel-litefs/blob/main/etc/litefs.yml) ) [read](/litefs/config) by LiteFS: ```yml theme={null} fuse: # Point to the directory containing your DB_DATABASE in fly.toml dir: "/var/www/html/storage/database" # Allow none-root access to the directory above--required for web access allow-other:true # Start the server, this was previously run in .fly/entrypoint.sh exec: "supervisord -c /etc/supervisor/supervisord.conf" ``` 5. Include a ( [etc/fuse.conf](https://github.com/fly-apps/fly-laravel-litefs/blob/main/etc/fuse.conf) ) file to enable `allow-other` option above to work. 6. Revise the generated [Dockerfile](https://github.com/fly-apps/fly-laravel-litefs#dockerfile) to include the `LiteFS` image, install required packages, and copy the configuration files `etc/litefs.yml` and `etc/fuse.conf` to a [proper location](/litefs/config#config-file-search-path) `LiteFS` can read. 7. Create a [middleware](https://github.com/fly-apps/fly-laravel-litefs/blob/main/app/Http/Middleware/FlyReplayLiteFSWrite.php) to forward write requests to the [primary node](/litefs/primary) using [fly-replay](/networking/dynamic-request-routing#how-fly-replay-works) 8. Use a non-file session driver to [allow fly-replay to work](https://github.com/fly-apps/fly-laravel-litefs#fly-replay-and-csrf-tokens) on Laravel CSRF protection. # Laravel: Multiple Fly Applications Source: https://docs.fly.io/laravel/advanced-guides/multiple-applications Run a Redis server as a separate Fly.io app inside your Laravel project folder, with its own fly.toml, password secret, and volume, reached over .internal DNS. This guide discusses how to manage multiple Fly applications within a Laravel project. This is useful for Laravel projects that need to run other services, like running a Redis service for queues, cache, and session storage. ## What is a Fly application? Your Laravel application is a Fly application, which means it has the following few things: * A `fly.toml` file in the root directory * A `Dockerfile` in the root directory that describes the image * A "server" running on Fly's infrastructure. So how do you spin up multiple Fly applications for a single Laravel project? ## Creating a Fly application within a Fly application The important thing about creating multiple Fly applications within a project is keeping them organized. For our example, we'll setup a Laravel server application and keep it in the `./fly/applications` folder within our project repo. Let's get started by running the following commands: ```bash theme={null} # Further reference: https://fly.io/laravel-bytes/full-stack-laravel/ mkdir -p fly/applications/redis cd fly/applications/redis ``` From inside the `fly/applications/redis` folder, run: ```bash theme={null} fly launch --image flyio/redis:6.2.6 --no-deploy --name my-project-name-redis ``` This command will create a `Dockerfile` and `fly.toml` file that can be further configured for your application's needs. Next, we can do some light configuration and then deploy the application: ```bash theme={null} # Add a required redis password # (note this means using this password when connecting to Redis from your apps) fly secrets set REDIS_PASSWORD=mypassword # Add a volume so deploys of this redis app dont lose data flyctl volumes create redis_server # Configure the volume mount in fly.toml cat <> fly.toml [mounts] source = "redis_server" destination = "/data" EOT # Deploy it! fly deploy ``` ## Accessing from the root application Fly creates DNS hosts for each of your applications based on the application name. For this example, we can reach the Redis instance via hostname `my-project-name.internal`. ## Deploying updates In the future, when it's time to deploy updates to your Fly application within a Fly application, run: ```bash theme={null} cd fly/application/redis fly deploy ``` That's it. # Using Inertia SSR Source: https://docs.fly.io/laravel/advanced-guides/using-inertia-ssr Deploy a Laravel Inertia app with server-side rendering on Fly.io by adding Node to the Dockerfile and running the SSR server as its own process group. [Inertia](https://inertiajs.com/) allows you to work with your Laravel monolith application, *and still* use frontend libraries and frameworks such as [React](https://react.dev/), [Vue](https://vuejs.org/), and [Svelte](https://svelte.dev/)! Furthermore, it supports [Server-side Rendering](https://inertiajs.com/server-side-rendering), which allows HTML content to be rendered from the server instead of the client. Once you've completed the [Server-side Rendering setup](https://inertiajs.com/server-side-rendering) for your local app, dive in below to deploy your server-side rendered, Laravel Inertia-SPA at Fly.io! *** ## Dockerfile Changes [Inertia SSR](https://inertiajs.com/server-side-rendering) requires a background [Node process](https://inertiajs.com/server-side-rendering#:~:text=Node%20must%20be%20available) that will render requested html pages. You'll have to update Fly.io's generated `Dockerfile` for your Laravel app in order to include support for `Node`. You can follow any of the below options to include `Node`: ### Option 1: Quick copy of Node to Final Image The Laravel fly-scanner already generates a Dockerfile that makes use of a `Node` image, but uses a different final image. The quickest way to include Node into your app's final image, is to copy over the `Node` image's `node` binaries and generated `node_modules` to the final `fly-laravel` image: ```dockerfile theme={null} FROM base + COPY --from=node_modules_go_brrr /usr/local/bin/node /usr/local/bin/node + COPY --from=node_modules_go_brrr /app/node_modules /var/www/html/node_modules ``` ### Option 2: Install Node in Final Image Of course, you can also instead opt to fully install `Node` in your final `fly-laravel` image. So, revise your Dockerfile to include instructions to install `node`, and build the necessary assets/node\_modules into your final image: ```dockerfile theme={null} # Install node in fly-laravel image RUN cd ~ \ && curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh \ && bash nodesource_setup.sh \ && apt install nodejs \ && cd /var/www/html # The snippet below already exists in the Dockerfile btw, # In the multi-stage build set up: RUN if [ -f "vite.config.js" ]; then \ ASSET_CMD="build"; \ else \ ASSET_CMD="production"; \ fi; \ if [ -f "yarn.lock" ]; then \ yarn install --frozen-lockfile; \ yarn $ASSET_CMD; \ elif [ -f "pnpm-lock.yaml" ]; then \ corepack enable && corepack prepare pnpm@latest-8 --activate; \ pnpm install --frozen-lockfile; \ pnpm run $ASSET_CMD; \ elif [ -f "package-lock.json" ]; then \ npm ci --no-audit; \ npm run $ASSET_CMD; \ else \ npm install; \ npm run $ASSET_CMD; \ fi; RUN chown -R www-data:www-data /var/www/html/public EXPOSE 8080 ENTRYPOINT ["/entrypoint"] ``` Since we now have node and its assets/modules in the `fly-laravel` image, we can remove our multi-build setup where we use a separate `FROM node` image ( to build and copy assets and node\_modules ): ```dockerfile theme={null} # Remove the "multi stage build setup" lines below: - FROM node:${NODE_VERSION} as node_modules_go_brrr - RUN mkdir /app - RUN mkdir -p /app - WORKDIR /app - COPY . . - COPY --from=base /var/www/html/vendor /app/vendor # Remove the "set up to copy asset folder from node image to fly-laravel image" # lines below: - COPY --from=node_modules_go_brrr /app/public /var/www/html/public-npm - RUN rsync -ar /var/www/html/public-npm/ /var/www/html/public/ && rm -rf /var/www/html/public-npm ``` ## Running SSR as A Process After getting your Dockerfile set up for Inertia SSR to work, you can run the SSR Server as a [background process](https://inertiajs.com/server-side-rendering#:~:text=server%20as%20a-,background%20process,-%2C%20typically%20using%20a). Here at Fly.io, you can easily run a separate VM for your SSR server instead of a monitoring tool like Supervisor. Update your `fly.toml` file to include an `ssr` [process group](/launch/processes#run-multiple-processes): ```toml theme={null} [processes] app="" ssr="php /var/www/html/artisan inertia:start-ssr" ``` Adding the SSR process above creates a new machine specially for your SSR server. You'll now have separate machines for your web app, and ssr server: ![Machines created for the processes](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/laravel-app-ssr-vms.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=ceac8775922292429ea37db5504c3341) Notice there are four machines above? The two machines listed at the bottom are back ups. Now that you have more than one process group for your Fly.io app, you'll have to make sure that the [`[http_service]`](/reference/configuration#the-http_service-section) is properly mapped to your `app` process. Update your `fly.toml`'s `http_service` section with the `app` process: ```toml theme={null} [http_service] internal_port = 8080 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 + processes = ["app"] ``` ### Web and SSR Communication The SSR server will be created as a separate VM from your web app. You'll have to configure your Laravel web VM to [talk with it](https://community.fly.io/t/process-group-aware-internal-dns-route-between-processes-with-ease/13063/4). To do so, first revise the `fly.toml` file to include an `SSR_URL` that will contain the ssr process' [.internal address](/networking/private-networking#fly-io-internal-dns) routed to SSR's applicable port ( in the case below, 13714): ```.env theme={null} [env] SSR_URL="ssr.process..internal:13714" // other envs here ``` Finally, pull the [inertia.php](https://github.com/inertiajs/inertia-laravel/blob/master/config/inertia.php) config file into your config directory, and update it to use the `SSR_URL` env variable: ```php theme={null} /* config/inertia.php */ 'ssr' => [ 'enabled' => true, 'url' => env('SSR_URL','http://127.0.0.1:13714') ], ``` With that, do a quick `fly deploy` and you're good to go! # Using Symfony Source: https://docs.fly.io/laravel/advanced-guides/using-symfony Deploy a Symfony app on Fly.io by letting fly launch treat it as Laravel, then disabling the .env requirement and trusting the Fly Proxy in Symfony config. Credit where credit's due, community member [Yaeger wrote this up](https://dejager.sh/articles/deploying-symfony-on-fly) and we're using it as our source. To run a Symfony app on Fly.io, we can trick `flyctl` into thinking it's a Laravel app, having it generate the needed configuration for us, and then tweak it as needed. ## Generating Configuration We'll start by tricking `flyctl` into thinking we have a Laravel application. The `fly launch` command just searches for a file named `artisan`. If it finds that, it will assume we have a Laravel application. ```bash theme={null} touch artisan fly launch ``` This will generate a Dockerfile and other related files (within a new `.fly` directory) that are needed to serve a PHP application. ## Environment Variables By default, Symfony wants a `.env` file to exist. That's fine if you want to include one in your build (you'll need to remote `.env` from the `.dockerignore` file if so). However we prefer to add environment variables to the `fly.toml` file (and have secrets be set via `fly secrets`). To accomplish this, we'll create a custom `SymfonyRuntime` class and use that when we deploy to file. Create file `.fly/FlySymfonyRuntime.php` with the following contents: ```php theme={null} && composer install... cp .fly/FlySymfonyRuntime.php /var/www/html/src/FlySymfonyRuntime.php ``` We also need to tell Symfony to use the `FlySymfonyRuntime` by setting an environment variable in `fly.toml`: ```toml theme={null} [env] APP_RUNTIME = '\App\FlySymfonyRuntime' ``` ## The Fly Proxy Symfony needs to be aware of the Fly Proxy to generate links correctly (among other things). The Fly proxy is a routing layer (similar to a load balancer) that sits between your application server and the public internet. The proxy terminates SSL connections, and sends "http" requests to your application. We need to tell Symfony to [trust this proxy](https://symfony.com/doc/current/deployment/proxies.html) by editing file `config/packages/framework.yaml`: ```yaml theme={null} framework: trusted_proxies: '127.0.0.1,REMOTE_ADDR' ``` ## Detail Work That's basically it! From then on, running `fly deploy` should let you deploy the application and run your Symfony application. If you want to see examples of running queues or connecting to a database, check out [this article](https://dejager.sh/articles/deploying-symfony-on-fly) which has details on that. # Database guides Source: https://docs.fly.io/laravel/database-guides Connect a Laravel app on Fly.io to MySQL, PostgreSQL, Redis or SQLite, and the different ways to run php artisan migrate on deploy. No application is complete without a data store! In this section, you'll find references on how to connect your Laravel application to Databases running as Fly Apps or as managed solutions from services that suits global distribution of data. Lastly, you can check the last section, "Laravel Database Migration" for a guide on how to run the classic Laravel command, "php artisan migrate", in Fly.io. ## Laravel and MySQL Notes and configurations on connecting with the relational database classic, MySQL ## Laravel and PostgreSQL Notes and configurations on connecting with the object-relational database system, PostgreSQL ## Laravel and Redis Notes and configurations on connecting with the “in-memory data store”, Redis ## Laravel and SQlite Notes and configurations on using the bundle-able SQL database engine, SQLite ## Laravel Database Migration Different ways to run the classic Laravel database migration command with Fly.io # Laravel Database Migration Source: https://docs.fly.io/laravel/database-guides/laravel-db-migrations Run php artisan migrate for a Laravel app on Fly.io four ways: a release_command in fly.toml, a startup script, or two fly ssh console methods. Make sure you have a database connection with your Laravel Fly App. You can check out our various connection guides on [`MySQL`](/laravel/database-guides/laravel-mysql), [`Postgres`](/laravel/database-guides/laravel-pgsql), [`Redis`](/laravel/database-guides/laravel-redis), and [`SQLite`](/laravel/database-guides/laravel-sqlite). Once you're set up with a database connection, there are several ways to run the Laravel classic: "`php artisan migrate`" command at Fly.io: \#1 Through the `fly.toml` \[deploy] configuration ( which will run in every deployment ) ```toml theme={null} [deploy] release_command = "php /var/www/html/artisan migrate --force" ``` \#2 Through a [Start Script](/laravel/the-basics/customizing-deployments#startup-scripts) in `.fly/scripts`: ```bash theme={null} echo "/usr/bin/php /var/www/html/artisan migrate --force" > ".fly/scripts/db.sh" ``` \#3 Through `fly ssh console` shortcut: ```bash theme={null} fly ssh console -C "php /var/www/html/artisan migrate --force" ``` \#4 Through `fly ssh console`: ```bash theme={null} fly ssh console cd /var/www/html/ php artisan migrate --force ``` # Laravel and MySQL Source: https://docs.fly.io/laravel/database-guides/laravel-mysql Connect a Laravel app on Fly.io to MySQL running as a Fly app or on PlanetScale, using .internal addresses, secrets, and fly proxy for local access. For data store, why not start with a relational database classic: [MySQL](https://www.mysql.com/). You can run your own MySQL Fly App, or set up a PlanetScale MySQL-compatible serverless database. ## *Laravel and MySQL Fly App* To run MySQL as a [Fly App](/app-guides/mysql-on-fly), follow our guide [here](/app-guides/mysql-on-fly). Afterwards, you're good to connect: 1. Connect to your MySQL Fly App from a Laravel Fly App 2. Connect to your MySQL Fly App from a local environment ### *Connect from a Laravel Fly App* 1. Revise the `[env]` configuration in your Laravel application's `fly.toml` file to connect with your MySQL Fly App's [Fly .internal address](/networking/private-networking#fly-io-internal-dns): ```toml theme={null} [env] APP_ENV = "production" DB_CONNECTION = "mysql" DB_HOST = "" DB_DATABASE= "" ``` 2. Then, set up your Laravel Fly App's database username and password through [Fly Secrets](/flyctl/cmd/fly_secrets): ```bash theme={null} fly secrets set DB_USERNAME= DB_PASSWORD= ``` 3. Finally deploy your Laravel Fly App changes with: ```bash theme={null} fly deploy ``` ### *Connect from a local environment* The MySQL instance you spun up in Fly.io "[is closed to the public internet](/machines/api/machines-resource#notes-on-networking)", and can only be accessed by another application found in your Fly.io organization's private network. You'll need a way to tunnel into the network and finally connect to your MySQL instance. **In this guide you'll tunnel to your MySQL instance through the use of `fly proxy`** 1. Open your MySQL application's `fly.toml` and take note of the following: ```toml theme={null} app = "" [env] MYSQL_DATABASE = "" MYSQL_USER = "" ``` 2. Then use `fly proxy` to tunnel to your MySQL application: ```bash theme={null} fly proxy 3306 -a ``` 3. Finally, update your Laravel application's local .env file with the values from your MySQL `fly.toml` file: ```env theme={null} DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE= DB_USERNAME= DB_PASSWORD= ``` ## *Laravel with MySQL-compatible PlanetScale* For a basic PlanetScale and Fly.io connection, follow [our guide here](/app-guides/planetscale). If you're up for a multi-region level up, check out our [Multi-Region Laravel with PlanetScale](https://fly.io/laravel-bytes/multi-region-laravel-with-planetscale/) article. Once you're setup with PlanetScale, connect your Laravel application in Fly.io through the following steps below: 1. Get Laravel connection information from PlanetScale instance 2. Connect from Laravel application in Fly.io ### *Get Laravel connection from PlanetScale instance* Once initialized, your database dashboard should have metrics and options like so: ![PlanetScale initialize database dashboard](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_laravel_new_database.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=56634b21cff3fb46bf529920213417bb)
  1. Click on the Connect button at the top right, this should provide a box of information for connecting with your PlanetScale database.
  2. First though, make sure to add a password, by clicking on the "New Password" button at the upper right corner. This should show you a new password afterwards.
  3. Next, select "Laravel" in the list labeled "Connect with"
![PlanetScale Laravel connection string](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/planetscale_laravel_connection_string.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=ff49a0c43b0cdde261951f17412bf74d) Take note of the connection string provided and let's move on! ### *Connect from a Laravel Fly App* 1. Update the `[env]` configuration in Laravel application's `fly.toml` with details from the PlanetScale connection string ``` [env] APP_ENV = "production" DB_CONNECTION = "mysql" DB_HOST = "" DB_DATABASE= "" MYSQL_ATTR_SSL_CA="/etc/ssl/certs/ca-certificates.crt" ``` Take note that the value for `MYSQL_ATTR_SSL_CA` varies depending on the Docker container used. For the default Docker container used by Fly.io, the above value is the path 2. Next, set up the database username and password through [fly secrets](/flyctl/cmd/fly_secrets): ```bash theme={null} fly secrets set DB_USERNAME= DB_PASSWORD= ``` 3. Finally deploy your changes with: ```bash theme={null} fly deploy ``` # Laravel and PostgreSQL Source: https://docs.fly.io/laravel/database-guides/laravel-pgsql Connect a Laravel app on Fly.io to Postgres with fly postgres attach or manual setup, set DB_URL for Laravel 11, and connect from your local machine. We have a *[whole section](/unmanaged-postgres)* dedicated to [PostgreSQL](https://www.postgresql.org/), we even have a `flyctl` integration to simplify both creation and interaction with your Postgres Fly Apps! ## *Laravel and PostgreSQL Fly App* To run PostgreSQL as a [Fly App](/app-guides/mysql-on-fly), follow our guide [here](/unmanaged-postgres/getting-started/create-pg-cluster). Afterwards you can connect it with your Laravel Fly App through various means: 1. Connect From a Laravel Fly App by Attachment 2. Connect From a Laravel Fly App Manually 3. Connect From a Local Environment ### *Connect From a Laravel Fly App by Attachment* 1. You can [Attach your Laravel Fly App to your Postgres Fly App](/unmanaged-postgres/managing/attach-detach) with `fly postgres attach`: * This will create a database named after `` in your Postgres Fly App * This will create an env variable `DATABASE_URL` containing a connection string in your Laravel fly app 2. Please make sure the `fly.toml` of your Laravel Fly App uses the postgres driver by specifying it as the value of `DB_CONNECTION`: ```toml theme={null} [env] APP_ENV = "production" DB_CONNECTION = "pgsql" ``` Thanks to the attach command, Postgres connection will now be available to your Laravel Fly App using the auto-generated `DATABASE_URL` from the attachment process. 3. If you are using Laravel version 11, it no longer uses the `DATABASE_URL` env variable name, instead it uses [DB\_URL](https://laravel.com/docs/11.x/database#:~:text=\(or%20corresponding-,DB_URL,-environment%20variable\)%20configuration). So, please set an additional Fly secret, `DB_URL` with the value of the `DATABASE_URL`. ```bash theme={null} fly secrets set DB_URL="" ``` ### *Connect From a Laravel Fly App Manually* If you decide not to use the `fly postgres attach` command above and would want to connect to your Laravel Fly App in a manual, *grueling* step-by-step procedure, I hear you! You'll have to: 1. Manually set up a Database in your Postgres Fly App 2. Manually connect your Postgres Database with your Laravel Fly App #### *Manually set up a Database in your Postgres Fly App* Manually connecting with your Postgres Fly App also means manually creating your Postgres Database. You can interact with your Postgres Fly App using `fly postgres connect`. 1. Connect with your Postgres Fly App using its App name. ```bash cmd theme={null} fly postgres connect -a ``` ```output output theme={null} Connecting to .internal... complete psql (14.4 (Debian 14.4-1.pgdg110+1)) Type "help" for help. postgres=# ``` In case you've forgotten about your app name, but managed to save the output from the Postgres Fly App creation (which you should've!), then check the value of the `Hostname`. The App name would simply be every little character before ".internal". 2. Great, you're connected! Go ahead and complete your set-up with a new database: ```sql theme={null} create database ; ``` 3. Now if you check your database list, you should find the recent database created: ```bash cmd theme={null} SELECT datname FROM pg_database; ``` ```output output theme={null} datname ~----------- postgres testdb template1 template0 (4 rows) ``` There are tons of ways to interact with your Postgres Fly App! If you want to get a bird's eye view of your Postgres database list outside of the `flyctl postgres connect` command, simply run the command below instead. ```bash theme={null} flyctl postgres db list -a ``` Now that you have a database in your Postgres Fly App, it's time to connect it with your Laravel Fly App. #### *Manually connect to Laravel Fly App* Once you have your running and *[database configured](/laravel/database-guides/laravel-pgsql#manually-set-up-a-database-in-your-postgres-fly-app)* Postgres Fly App, it's time to connect with your Laravel Fly App: 1. Revise your Laravel Fly App's `fly.toml` file to connect with the `Hostname`( your Postgres Fly App's .internal Address ) from the configuration output, and the `DB_NAME` configured above: ```toml theme={null} [env] APP_ENV = "production" DB_CONNECTION = "pgsql" DB_HOST = "" DB_DATABASE= "" DB_PORT = 5432 ``` 2. Set your Laravel database `DB_USERNAME` and `DB_PASSWORD` with `fly secrets` based on the configuration output received during your Postgres Fly App creation: ```bash theme={null} fly secrets set DB_USERNAME= DB_PASSWORD= ``` 3. You've *finally* reached the last part. You can now rest easy and deploy: ```bash theme={null} fly deploy ``` ### *Connect From a Local Environment* 1. You can check out various ways to connect with your Postgres Fly App from your local environment, [here](/unmanaged-postgres/connecting/connecting-with-flyctl). 2. Once you're locally connected to your Postgres Fly App, setup your Laravel .env file to connect with the Postgres database: ```sh theme={null} DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE= DB_USERNAME= DB_PASSWORD= ``` #### *Laravel Possible PGSQL Errors* **If you get an error similar to, `"ERROR: could not find driver pgsql"`, make sure that your environment is configured properly with a pgsql driver:** 1. First up, check your php.ini file and make sure the driver for Postgres is available and *uncommented*: ``` extension=pgsql ``` 2. Next, make sure you have the correct **php-pgsql** installed for your PHP version. Let's assume you have a PHP 8.1 running locally, then you must also have the compatible php8.1-pgsql in your environment. **If you get an error similar to, `"pg_dump: error: server version: 14.x; pg_dump version: 12.x"`, the pgsql client running in the VM does not match the server version.** * To fix this, update your `Dockerfile`. In there, you can install an updated version of the PostgreSQL clients by adding the following commands: ```dockerfile theme={null} # Install pgsql client for version 14 RUN ...other stuff here... \ && apt-get -q install -y lsb-release wget \ && echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ && apt-get -q update \ && apt-get -q install -y postgresql-client-14 ``` ## *Test Connection* To test whether you are connected to your sparkling new Postgres Fly App, a simple `php artisan migrate` should let you know. Once migration completes, you can check the tables migrated in your Postgres Fly App: ``` # Connect to your Postgres Fly App $ fly postgres connect -a # Use the database created postgres=# \c You are now connected to database "" as user "". # List your current database's tables =# \d ``` # Laravel and Redis Source: https://docs.fly.io/laravel/database-guides/laravel-redis Connect a Laravel app on Fly.io to Redis, either a self-hosted Redis Fly app with a volume and password or Upstash Redis, from production and locally. [Redis](https://redis.io/) is a NoSQL database popularly used for cache storage, as a message broker, and even as a primary database. You can run your own Redis Fly App, or get one running and managed through Upstash Redis. ## *Laravel and Redis Fly App* You can set up your own Redis Fly App either through Fly.io's Redis Docker Image, or through Redis' Official Docker Image. ### *Fly.io Redis Docker Image* 1. First create a new directory and initialize it with `fly launch`, using [Fly.io's Redis Image](https://hub.docker.com/r/flyio/redis/tags) for the build: ```bash theme={null} mkdir fly-redis cd fly-redis fly launch --image flyio/redis:6.2.6 --name fly-redis --region ams --no-deploy ``` * Pull the **flyio/redis:6.2.6** image from the Docker repository through `--image` argument. * You can specify your Redis Fly App name through the `--name` argument * You can specify your region code through the `--region` attribute * You can opt not to immediately deploy by adding `--no-deploy` argument 2. Afterwards, add a [volume](/volumes) that will persist your Redis data ```bash theme={null} fly volumes create redis_server --size 1 ``` * You can specify any volume name to replace redis\_server above * You can specify your preferred volume size in GB through the `--size` argument 3. Then, attach your volume to your Redis Fly App by revising its `fly.toml` to include `[[mounts]]`: ```toml theme={null} app = "fly-redis" # Your primary region may differ primary_region = "ams" [build] image = "flyio/redis:6.2.6" [[mounts]] destination = "/data" source = "redis_server" ``` * Under `[[mounts]]` attach the volume created in step #2, make sure that the name specified for the created volume is exactly the same string specified for its `source` attribute 4. Next, set up the Redis Fly App **password** through `fly secrets` ```bash theme={null} fly secrets set REDIS_PASSWORD= ``` 5. Finally, deploy your Redis Fly App! ```bash theme={null} fly deploy ``` ### *Redis Official Docker Image* If you would like to use [Redis' official docker image](https://hub.docker.com/_/redis) to set up your Redis Fly App, the steps to get up and running are almost identical to the previous guide. There is just one minor revision on the image pulled and an additional step to set the Redis Fly App's password. 1. Follow the steps [above](#fly-io-redis-docker-image), with a revision to the value passed for `--image` ```bash theme={null} mkdir off-redis cd off-redis fly launch --image redis --name off-redis --region ams --no-deploy ``` * Pull the **redis** image from the Docker repository through the `--image` argument. 2. Before deploying, make sure to revise your `fly.toml` file to specify your Redis Fly App's password. Add the `[processes]` section for the default `app` process and add the following commands: ```toml theme={null} [processes] app = "sh -c exec redis-server --requirepass \"$REDIS_PASSWORD\"" ``` * Use the `fly secret` **`REDIS_PASSWORD`** you've configured earlier * If this passphrase is not set and your Laravel configuration sends a REDIS\_PASSWORD during its connection to your Redis Fly App, watch out! You'll get the infamous "ERR Client sent AUTH, but no password is set" error ### *Connect from a Laravel Fly App* Now that you have your Redis Fly App running, the next step is to connect it with your Laravel Fly App! Follow the steps below: 1. First, retrieve your Redis Fly App's [Fly .internal Address](/networking/private-networking#fly-io-internal-dns). 2. Next, make sure you move to your Laravel Fly App's directory for better navigation: ```bash theme={null} cd ``` 3. Then revise your Laravel Fly App's `fly.toml` `[env]` configuration with the retrieved Fly .internal Address: ```toml theme={null} [env] ... REDIS_HOST= ".internal" CACHE_DRIVER= "redis" ``` 4. Make sure you set your Laravel Fly App's `REDIS_PASSWORD` through the `fly secrets` command: ```bash theme={null} fly secrets set REDIS_PASSWORD= ``` 5. Finally, deploy! ```bash theme={null} fly deploy ``` That's it! Your Laravel and Redis Fly Apps should now be connected. ### *Connect from a local environment* The simplest way to connect your local Laravel application to a Redis Fly App is to use `fly proxy`: 1. Open your Redis Fly App's local directory ```bash theme={null} cd off-redis ``` 2. Then run the `fly proxy` for port 6379 or whichever REDIS\_PORT you have configured: ```bash theme={null} fly proxy 6379 ``` 3. Next, revise your local Laravel application's .env file to update your Redis connection: ```env theme={null} CACHE_DRIVER=redis REDIS_HOST=127.0.0.1 REDIS_PASSWORD= REDIS_PORT=6379 ``` Make sure the REDIS\_PORT in your .env configuration matches the port used in `fly proxy` That's it! Your local Laravel application should now be connected with your Redis Fly App. ## *Laravel with Upstash Managed Redis Fly App* Want a fully-managed Redis Fly App? Try [Upstash Redis](https://upstash.com/)! To set up, you can follow our-in-depth guide [here](/upstash/redis) Once you've configured the necessary details through the flyctl prompts, and the app deployment completes, you should get a summary of the Redis cluster you've just deployed, like so: ```output theme={null} Your Upstash Redis database blue-brook-3843 is ready. Apps in the personal org can connect to at redis://default:@fly-blue-brook-3843.upstash.io If you have redis-cli installed, use fly redis connect to connect to your database. ``` ### *Connect From a Laravel Fly App* To test whether your Laravel Fly App can successfully connect with your Upstash Redis database, you can use your Upstash Redis Fly App as your Cache driver, then perform a quick cache:clear command. 1. Revise the `[env]` configuration in your Laravel Fly App's `fly.toml` file: ```toml theme={null} [env] ... CACHE_DRIVER="redis" REDIS_URL="redis://default:@fly-blue-brook-3843.upstash.io" REDIS_CACHE_DB=0 ``` * Set the Laravel cache driver to redis * Using `REDIS_URL` should override `REDIS_HOST`, add here the `REDIS_URL` received during `fly redis create` * Upstash only supports "database 0", so update your `REDIS_CACHE_DB` config to 0. Otherwise, you will receive an error : `SELECT failed: ERR Only 0th database is supported! Selected DB: 1...` 2. Then deploy your `fly.toml` changes: ```bash theme={null} fly deploy ``` ## Testing Connection In all examples above, Redis was used as our Laravel application's Cache Driver. To test if this connection is working or not, a simple `php artisan cache:clear` should let you know. # Laravel and SQlite Source: https://docs.fly.io/laravel/database-guides/laravel-sqlite Use SQLite as the database for a Laravel app on Fly.io: set DB_CONNECTION in fly.toml, keep the file on a volume, and look to LiteFS for multiple Machines. "[SQLite](https://www.sqlite.org/about.html)" is a file-based, lightweight, low-latency, and in-process library providing an embeddable SQL database engine. This guide walks through a single instance of your Laravel Fly App running with SQLite as its main database. ### Set up SQLite in the Storage Folder Since SQLite is "embeddable", you no longer have to set up a separate Fly App for it. You simply configure your Laravel Fly App with one: 1. Configure SQLite connection in your fly.toml file: ```toml theme={null} [env] ... DB_CONNECTION="sqlite" DB_DATABASE="/var/www/html/storage/database/database.sqlite" ``` 2. As seen above, the SQLite database will be located in the storage directory. Make sure to mount a volume on the directory by following [this guide here](/laravel/the-basics/laravel-volume-storage). [Mounting a volume](/launch/volume-storage) on the storage folder will ensure everything it contains gets persisted, including data on the SQLite database located inside. 3. Once you've configured persisting the storage directory by following step 2 above, revise the start up script that will be created from it. Revise it to enable initializing the SQLite file: ```bash theme={null} # .fly/scripts/1_storage_init.sh # Add this below the storage folder initialization snippet FOLDER=/var/www/html/storage/database if [ ! -d "$FOLDER" ]; then echo "$FOLDER is not a directory, initializing database" mkdir /var/www/html/storage/database touch /var/www/html/storage/database/database.sqlite fi ``` The condition statement checks if the database folder does not exist in the volumized storage folder. If it does not exist, it creates a database directory inside storage/ and creates a database.sqlite file in the storage/database folder. 4. Finally, deploy your Laravel Fly App with `fly deploy`! ### Multi-Node SQLite with LiteFS Do you have multiple instances set up for your Fly app? Data between those instances' SQLite databases will not automatically be in sync with each other. You'll have to configure data replication across those instances' SQLite databases by yourself. Of course, manual setup is not a requirement—you can instead rely on the *power* of [LiteFS](/litefs). LiteFS provides data replication across different SQLite databases of your Fly app. All you’ll need is to set it up on your Fly app, and it will do the replication for you. You can read more about the [getting started guide here](/laravel/advanced-guides/global-sqlite-litefs), and also check out our Laravel section’s quick notes on setting LiteFS on a [Laravel Fly app here](/laravel/advanced-guides/global-sqlite-litefs). # Laravel on Fly.io Source: https://docs.fly.io/laravel/index Deploy a Laravel app to Fly.io with fly launch, which detects Laravel, offers Postgres, Redis and Tigris, and generates a Dockerfile, fly.toml and .fly/. ![](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/laravel-intro.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=9a7f1026c50dff6d356d685c0ce18392) Getting an application running on Fly.io is essentially working out how to package it as a deployable image. Once packaged, it can be deployed to the Fly.io platform. In this guide we’ll learn how to deploy a [Laravel](https://laravel.com/) application on Fly.io. For an overview of how to configure your application with services like caching, databases, and queues, reading through our [Full Stack Laravel](https://fly.io/laravel-bytes/full-stack-laravel/) post is the best place to start. ## Prepare a Laravel app Bring your own Laravel app, or create a new one! If you want to start fresh, here's how to set up a new application. You'll need PHP 8+ and [composer](https://getcomposer.org/) installed locally. You can check your PHP version using `php --version`. ```bash theme={null} composer create-project laravel/laravel fly-laravel cd fly-laravel php artisan serve ``` You should be able to visit `http://localhost:8000` and see the home page. ## Deploy to Fly.io ### Install Fly First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. ### Launch 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`!** ```bash theme={null} 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](/postgres/index) ( database ), [Redis](/upstash/redis) ( cache ), and [Tigris](/tigris) ( storage ) can also be added by default based on whether there's an existing, similar connection found for your Laravel project. > [!WARNING] > > **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 extension 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. That's it! Run `fly apps open` to see your deployed app in action. Try a few other commands: * [`fly logs`](/flyctl/cmd/fly_logs) - Tail your application logs * [`fly status`](/flyctl/cmd/fly_status) - View your app's current deployment status * [`fly ssh console`](/flyctl/cmd/fly_ssh_console) - Open a terminal on your VM * [`fly deploy`](/flyctl/cmd/fly_deploy) - Deploy the application after making changes ## Customizing your app If you have other environment variables to set, you can edit the `fly.toml` file and add them. ```toml theme={null} [env] # Set any env vars you want here # Caution: Don't add secrets here APP_URL = "https://fly-hello-laravel.fly.dev" ``` Replace this with the URL your app will be served on (by default, `"https://.fly.dev"`). For sensitive data, you can set **secrets** with the [`fly secrets set`](/flyctl/cmd/fly_secrets_set) command: ```bash theme={null} fly secrets set SOME_SECRET_KEY= ``` ### Deploy Once you've made changes for your app locally, you can run `fly deploy` to deploy your changes. ### Some Notes 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`). # Laravel: The Basics Source: https://docs.fly.io/laravel/the-basics Laravel basics on Fly.io: PHP and Node versions, custom deploys, stack trace logging, queues and the cron scheduler, a persistent storage folder, Tigris. Most Laravel applications require additional setup, including provisioning a database and cache. These guides will help you get through the basics of setting up your Laravel application with pieces of infrastructure commonly found in medium-to-high complexity applications. ## PHP and Node Versions Adjust the PHP and NodeJS versions used to build and run your Laravel application. ## Customizing Deployments Customize how Fly deploys your Laravel application. ## Post Deployment Interact with a deployed Laravel application. ## Logging Stack Traces Adjust Laravel’s logging configuration to get a full stack trace. ## Cron and Queues Run queue workers and Laravel’s scheduler via cron ## Persisting the Storage Folder Mount a volume to a re-initialized storage folder ## Tigris for CDN File Storage Integrate Tigris Bucket using Laravel S3 driver # Cron and Queues Source: https://docs.fly.io/laravel/the-basics/cron-and-queues Run Laravel's scheduler and queue workers on Fly.io by adding cron and worker process groups to fly.toml alongside the app process that serves web traffic. You may need to run Laravel's scheduler (via cron) or queue workers. The best way is to use the [processes](/reference/configuration#the-processes-section) configuration. This allows you to segment your application into "groups". Each "group" can run an instance of your code base with a specific use case (web server, cron tasks, queue worker). > If you used `fly launch` to create your Laravel application, all of this should work without further modification to the Docker setup. ## Scheduler (cron) We can start the `cron` daemon, which is pre-configured to run `php artisan schedule:run`. To enable this, edit your `fly.toml` file and add a `[processes]` section: ```toml theme={null} [processes] app = "" cron = "cron -f" ``` The line `app = ""` is needed (with an empty string!) to keep a process group for your application serving web requests. We created an additional process group `cron = "cron -f"`. This tells the VM to run the cron daemon rather than start the web server. Edit your `Dockerfile` to add additional cron definitions to `/etc/cron.d` if you need. The `cron -f` command will load any cron definitions found in any files in that location. ## Queue Worker We can start an instance of our queue worker by adding another process. In this example, we'll add a `worker` process group in addition to `app` and `cron` processes. ```toml theme={null} [processes] app = "" cron = "cron -f" worker = "php artisan queue:listen" ``` Note that you may want to expand on the `queue:listen` command. For example, you could run `php artisan queue:listen sqs --sleep=3 --tries=3 --max-time=3600` to use the `sqs` driver and determine other behaviors. ## Extra Credit There's a few bits to explain about the above. ### Command vs Entrypoint Internally, the things defined in the `[processes]` section are commands to run when the application instance is run. In Docker terminology, these are the Commands (`CMD`) being sent to the Entrypoint (`ENTRYPOINT`) as defined in the `Dockerfile`. That's why the `app` process group has a command of an empty string! We want the Entrypoint script to run without passing it any additional commands. The Entrypoint script will then perform it's default behavior of starting Nginx/PHP-FPM to serve web requests. The commands we defined are run relative to the Laravel project root, which is `/var/www/html` by default. This is configured in the `Dockerfile` used to setup the application. Lastly, don't forget that Fly.io builds a Docker image, and then converts that to a micro-VM run on Fly's hardware. ### Services The `[http_service]` (or `[[services]]`) section of the `fly.toml` should already contain the line `processes = ["app"]`, telling Fly to apply the service config to the application VM serving web requests. That will expose public ports and set health checks as you would want for the public-facing application. ```toml theme={null} [http_service] internal_port = 8080 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 processes = ["app"] ``` The queue and cron application instances don't need to expose public ports and don't support tcp-based health checks. Thus, we do not define any `services` for them. ### Scaling You can scale the process groups out separately. For example, this will create 2 `app` instances (load balanced automatically) and 4 queue worker instances. ```bash theme={null} fly scale count app=2 worker=4 ``` Check [the docs on processes](/reference/configuration#the-processes-section) for more examples on scaling separately (e.g. scaling server size and regions per process group). # Customizing Deployments Source: https://docs.fly.io/laravel/the-basics/customizing-deployments Customize Laravel deployments on Fly.io with startup scripts in .fly/scripts and a release_command for migrations, and detect release runs with RELEASE_COMMAND. The `fly launch` command sets you up with a useful starting point, but you can customize just about everything to meet your needs. Here's a few things that might be useful to know. ## Startup Scripts The `fly launch` command generated a `.fly/scripts` directory. Any file in here ending in `.sh` will run (via `bash`) anytime your application is started. These scripts are called from the Docker `ENTRYPOINT` script (`.fly/entrypoint.sh`) before anything else is ran. By default, `fly launch` generates a startup script `.fly/scripts/caches.sh`, which runs various artisan cache commands such as `config:cache`, `route:cache`, and `view:cache`. You can enable or disable those as you see fit, as well as add your own `.sh` scripts! ## Release Command You may want to use the [`release_command`](/reference/configuration#the-deploy-section) to perform database migrations or other tasks. The release command is run in a temporary VM that is created just before your application is deployed and released. Keep in mind that this temporary VM doesn't have access to volumes. This potentially helps with zero-downtime deploys. Note, however, that any Startup Script in `.fly/scripts` will also be run when a `release_command` is used. If you need a startup script to do something (or NOT do something) during a release command, your scripts can detect the presence of the `RELEASE_COMMAND` environment variable. ```bash theme={null} if [ -z "$RELEASE_COMMAND" ]; then # We are NOT in a temporary VM, run as normal... else # We are in the temporary VM created # for release commands... fi ``` Note that [release commands](/reference/configuration#the-deploy-section) run in a temporary VM. Any file-based changes done in the release command (such as `artisan view:cache`) will be lost when the release command is completed. The subsequently deployed application is a totally different VM. Commands that result in file-based changes (such as `artisan view:cache`) is best run as a Startup Script. # Tigris for CDN File Storage Source: https://docs.fly.io/laravel/the-basics/laravel-tigris-file-storage Store Laravel files in a Tigris bucket on Fly.io: create it with fly storage create, map its credentials to the S3 driver, and serve public files. [Tigris is an S3-compatible](https://www.tigrisdata.com/docs/overview/) storage service built on Fly.io infrastructure. It provides an intelligent cache-managing, data replication service that offers CDN-like behavior for your object storage with almost-zero configuration needed. The Laravel Flysystem integration provides a driver for working with Amazon S3-compatible services. Since Tigris supports the S3 API, Laravel's existing S3 driver can be used to connect with it. It's really easy to get started with Tigris. Just create a bucket, plug in its credentials to your Laravel app, upload your files, and users from around the globe should be able to access your files closer and faster thanks to Tigris' automatic data distribution feature. Try it out! ## The Tigris Bucket 1. Create a Tigris Bucket You can create a Tigris bucket through the `flyctl` cli. Pro tip: run this command inside a Fly.io initialized project, doing so will allow `flyctl` to automatically set secrets on your Laravel Fly app using the credentials of your Tigris Bucket: ```cli theme={null} // Open your project directory cd my-laravel-fly-initialized-app // Run the create command fly storage create ``` You'll be asked for a custom name for your bucket, but you can choose to create one with a random name. Once your bucket completes initialization, you'll receive its credentials in your console. If you did run the create command inside your Laravel Fly app's project directory, these secrets will automatically get set for your Laravel Fly app: ```cli theme={null} Setting the following secrets on : AWS_ACCESS_KEY_ID: AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev AWS_REGION: auto AWS_SECRET_ACCESS_KEY: BUCKET_NAME: ``` 2. Visit your Bucket Once you've taken note of the secrets set above, you can visit this newly created bucket by getting your console link using the `BUCKET_NAME` attribute received from above: ``` fly storage dashboard ``` Running the command above should give you a link to your bucket's console dashboard: ``` Opening https://console.tigris.dev/flyio/signin?org_id=6y84k.... ``` The link provided will lead straight to your Tigris console, where you should see the name of the bucket you've recently created. Of course, you'll have to be logged in via Fly.io to access this dashboard. ## Integrating Laravel with Tigris Now that we have our Tigris Bucket, let's connect it with our Laravel app. 1. Configure Laravel with S3 driver using Tigris credentials: Make sure you have the [Flysystem S3 package installed](https://laravel.com/docs/11.x/filesystem#s3-driver-configuration) for your Laravel project: ``` composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies ``` Let's use the credentials of our Tigris bucket as values for our S3 driver: | Tigris Credential | Laravel S3 Environment Variable | | - | - | | AWS\_ACCESS\_KEY\_ID | AWS\_ACCESS\_KEY\_ID | | AWS\_SECRET\_ACCESS\_KEY | AWS\_SECRET\_ACCESS\_KEY | | AWS\_REGION | AWS\_DEFAULT\_REGION | | BUCKET\_NAME | AWS\_BUCKET | | AWS\_ENDPOINT\_URL\_S3 | AWS\_ENDPOINT | In a local setup, all we have to do is update our .env file with the values above: ``` AWS_ACCESS_KEY_ID="" AWS_ENDPOINT="https://fly.storage.tigris.dev" AWS_REGION="auto" AWS_SECRET_ACCESS_KEY="" AWS_BUCKET="" ``` For a Laravel Fly app, the first two environment variables have already been set as secrets during the creation of the Tigris Bucket. What remains is to set the additional env variables that don't match: ``` // Please change the values enclosed in <> with the appropriate value received during the creation of the Tigris Bucket fly secrets set AWS_DEFAULT_REGION="" fly secrets set AWS_BUCKET="" fly secrets set AWS_ENDPOINT="" ``` 2. Upload a file from the Controller: To test out our connection to the Tigris bucket, we can create a sample function that uploads a text file to the bucket: ``` use Illuminate\Support\Facades\Storage; public function uploadTestFile() { Storage::disk('s3')->put('example.txt', 'Contents'); } ``` Calling the function above should upload a new file in your bucket. ## Serving public files If your bucket is [public](/tigris#public-buckets), uploaded objects are accessible without authentication at `https://bucket-name.t3.tigrisfiles.io/key-name`. You can use this URL directly in your Blade templates for images, CSS, JavaScript, and other assets. No credentials or pre-signed URLs are needed. For production use, we recommend setting up a [custom domain](https://www.tigrisdata.com/docs/buckets/custom-domain/) so your public URLs stay stable. See the [Tigris Public Bucket docs](https://www.tigrisdata.com/docs/buckets/public-bucket/) for details on all available public domains. # Persisting the Storage Folder Source: https://docs.fly.io/laravel/the-basics/laravel-volume-storage Persist the Laravel storage folder on Fly.io by creating a volume for each Machine, mounting it in fly.toml, and re-initializing the folder at startup. The [storage folder](https://laravel.com/docs/9.x/filesystem#the-local-driver) by default, holds several "generated" essentials of your Laravel application. It's the default burrow for session, cache, and file data amongst others. If you'd opt to persist data on this folder, you'll have to mount a [volume](/volumes) to it. > [!WARNING] > > Fly Volumes do not automatically sync their data with each other. Please remember to create the appropriate data-replication logic if your Fly App will be using more than one volume instance, and if your app requires data available across its Volumes. > [!NOTE] > > For file storage, you can utilize the S3-compatible [Tigris service](https://www.tigrisdata.com/docs/overview/)! > > Aside from offering S3-compatible storage buckets, it handles data distribution and cache management for you, providing CDN-like behavior for your application's file storage with almost zero-configuration needed. > > Check out its integration with Laravel [here](/laravel/the-basics/laravel-tigris-file-storage). ## The Steps 1. Make sure you are in your Laravel Fly App's directory ```bash theme={null} cd ``` 2. Then, check the number of machines currently available for your Fly App: ```bash theme={null} fly status ``` In this example today, the app is deployed in the AMS region, and has the [default number of two machines](/apps/app-availability#two-machines-for-process-groups-with-services) created for it: ```bash theme={null} Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 5683945bd46448 1 ams started 2023-10-03T09:54:04Z app e82d922f010908 1 ams started 2023-10-03T09:54:25Z ``` 3. To mount a volume named `"storage_vol"` to this Fly App, you'll have to create two `"storage_vol"` volumes in the AMS region, [one for each machine](/volumes/overview#volume-considerations): ```bash theme={null} fly volumes create storage_vol --region ams --count 2 ``` Running the command above should create two separate volumes with the name "storage\_vol", in the AMS region for your Laravel Fly App. 4. Next, revise your Laravel Fly App's `fly.toml` file to mount the Volumes above to each machine's storage folder: ``` [mounts] source="storage_vol" destination="/var/www/html/storage" ``` > [!NOTE] > > Mounting a Volume to a folder will initially erase any item the folder contains during the first time the Volume is mounted for the folder. > > For example, Laravel's storage folder contains subfolders: app, framework, and logs. > Mounting a volume to the storage folder erases these directories, and leaves behind a sole item paradoxically named as "lost+found". > > But, you wouldn't want to only be left with "lost+found" in your storage folder. You'd want to still have the necessary files and directories in there for successful session, views, caching, and file storage compliance with Laravel's default configuration. 5. To fix the little storage-content-erasure issue as stated in the callout above, please go ahead and make a copy of your storage folder in a "backup" folder. You can name this directory "storage\_". ```bash theme={null} cp -r storage storage_ ``` You'll later use this folder to copy over its contents to the volumized storage folder. 6. Next create a [Startup Script](/laravel/the-basics/customizing-deployments) that will initialize the volumized storage folder's contents. ```bash theme={null} touch .fly/scripts/1_storage_init.sh ``` Start up scripts are run in numeric-alphabetical order. Naming `1\_storage\_init.sh` makes sure it is the first script run. Otherwise, naming the file as `storage_init.sh` alone would've moved the `caches.sh` script above it, and would've executed before storage initialization happened. One of the commands in the `caches.sh` will not have worked properly, due to a lack of properly initialized storage directory. **On to the content of the Start Up script:** ```bash theme={null} FOLDER=/var/www/html/storage/app if [ ! -d "$FOLDER" ]; then echo "$FOLDER is not a directory, copying storage_ content to storage" cp -r /var/www/html/storage_/. /var/www/html/storage echo "deleting storage_..." rm -rf /var/www/html/storage_ fi ``` So what happened above? * The condition statement checks if the app folder does not exist in the volumized storage folder. If it does not exist, it copies over the contents of the storage\_ folder to the volumized storage folder. 7. **Finally, deploy your Laravel Fly App!** ```bash theme={null} fly deploy ``` #### ***Possible Errors*** ```output theme={null} Error not enough volumes named `` (1) to run `()` processes ``` The above error can come up after configuring your volume in `fly.toml` and executing `fly deploy`. It can mean that there are `` processes configured in your `fly.toml` trying to use the volume! Take note however, that a Volume can only be used by one at any given time. One way to fix this issue is to separate each process into [different Fly.io apps](/app-guides/multiple-processes#maybe-you-want-multiple-apps-though). Of course, separate application per process might require inter-communication between the applications. Fly.io applications can easily communicate with each other over a [private network](/networking/private-networking). Not only that, but Fly.io also offers the `fly-replay` [response header](/networking/dynamic-request-routing#how-fly-replay-works) which can be used to "redirect" request from one application to another, and return response from the correct application. # Logging Stack Traces Source: https://docs.fly.io/laravel/the-basics/logging-stack-traces Get full stack traces in Laravel logs on Fly.io by adding includeStacktraces to the stderr channel's formatter_with settings in config/logging.php. By default, we set the Logging output to use a `JsonFormatter`. This makes the log output a bit cleaner, but has the trade-off of not showing you a full stack trace. You just get the exception messages. If you need to get the full stack trace, update your `config/logging.php` file and adjust the `stderr` channel to include a `formatter_with` configuration: ```php theme={null} 'stderr' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), // JsonFormatter 'formatter_with' => [ 'includeStacktraces' => true, ], 'with' => [ 'stream' => 'php://stderr', ], ], ``` This will show you the full stack trace of logged errors when you next deploy. # PHP and Node Versions Source: https://docs.fly.io/laravel/the-basics/php-node-version Set the PHP and Node.js versions for a Laravel app on Fly.io with build args in fly.toml or --build-arg flags on fly deploy, and see the default versions. Your app is a special snowflake. Here are a few things you may want to customize further! ## Node and PHP Version We try to detect your local version of PHP and match it (with a minimum of PHP 7.4). If we can't determine the version, we default to version 8.2. For NodeJS, we default to version 18. You can define the PHP and NodeJS versions you want by adding/adjusting build arguments in the `fly.toml` file: ```toml theme={null} [build] [build.args] PHP_VERSION = "8.3" NODE_VERSION = "18" ``` Alternatively, you can set (or over-ride) the build arguments when deploying: ```bash theme={null} fly deploy --build-arg "PHP_VERSION=8.1" --build-arg "NODE_VERSION=14" ``` # Post Deployment Source: https://docs.fly.io/laravel/the-basics/post-deployment Manage a deployed Laravel app on Fly.io: open a shell with fly ssh console, view logs with fly logs or the dashboard, and keep log files on a volume. Once you have your Laravel application up and running in Fly.io, it helps to know how to execute commands on it and review events happening in your application. ## *Console Access* To interact with your Laravel application in Fly.io, you can ssh into a running instance through the [`Fly SSH Console`](/flyctl/cmd/fly_ssh_console): ```bash cmd theme={null} fly ssh console ``` ```output output theme={null} Connecting to top1.nearest.of.frosty-glitter-7650.internal... complete # ``` Once you gain access to your application's console, you can run commands like you would in your local environment, like listing your Laravel folder structure: ```bash cmd theme={null} cd /var/www/html ls -l ``` ```output output theme={null} Dockerfile artisan composer.lock docker package.json public storage vendor README.md bootstrap config lang phpunit.xml resources tailwind.config.js vite.config.js app composer.json database package-lock.json postcss.config.js routes tests ``` ## *Logs* Hopefully you have configured some neat, helpful [logs](https://laravel.com/docs/9.x/logging) to keep track of the events happening in your Laravel application. So another way to inspect what your application's been up to, is to check its application logs. There are several options: ### Fly.io's logging service By default, Fly.io updates Laravel's logging channel to `"stderr"` through the `[env]` configuration found in `fly.toml`: ```toml theme={null} [env] APP_ENV = "production" LOG_CHANNEL = "stderr" LOG_LEVEL = "info" LOG_STDERR_FORMATTER = "Monolog\\Formatter\\JsonFormatter" ``` This `"stderr"` configuration prints your application logs into the console, where the Fly.io service is able to listen and capture logs from. In order to **view your logs** captured by the Fly.io service, you can either: 1. Visit your application's monitoring section from your Fly.io dashboard like so: `"https://fly.io/apps//monitoring"` 2. Or run the [flyctl logs](/flyctl/cmd/fly_logs) command: ```bash cmd theme={null} fly logs ``` ```output output theme={null} 2022-09-23T12:05:07Z app[8ebaa037] ams [info]{"message":"reached test","context":{},"level":200,"level_name":"INFO","channel":"production","datetime":"2022-09-23T12:05:07.200105+00:00","extra":{}} ``` > [!NOTE] > > Take note however that Fly.io logs are kept only for **2 days**, before they're all wiped out! ### *Volumed Log File* #### Laravel Log File If you would like to persist your application logs for a longer duration, you can instead opt to save the logs in a log file by configuring `"single"`, `"daily"`, or `"stack"` as the **LOG\_CHANNEL**. So update your `fly.toml`: ```toml theme={null} [env] APP_ENV = "production" LOG_CHANNEL = "stack" LOG_LEVEL = "info" LOG_STDERR_FORMATTER = "Monolog\\Formatter\\JsonFormatter" ``` If you've opted to configure `"stack"` as your **LOG\_CHANNEL** make sure its `channels` configuration found in `config/logging.php` includes `"single"` or `"daily"`: ```php theme={null} 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], 'ignore_exceptions' => false, ], ``` Note: You can even add `"stderr"` in the stack's channels list to view your logs through Fly.io. > [!NOTE] > > Then, through the Fly SSH Console, you can access the logs in your application! #### Adding Volume Deployment wipes out all log files! In order to persist your Laravel log files, you'll need to add a [volume](/volumes) to persist your log folder's state. 1. First let's create a Volume: ```bash theme={null} fly volumes create my_log_vol --region ams --size 10 #GB ``` * You can replace the volume name `my_log_vol` with any name of your preference. * You can specify your region code through the --region attribute * You can specify your volume's size in GB through the --size attribute 2. Revise `fly.toml` to mount that volume in your `/var/www/html/storage/logs` folder: ```toml theme={null} [mounts] source="my_log_vol" destination="/var/www/html/storage/logs" ``` 3. Finally, deploy the changes! ```bash theme={null} fly deploy ``` > [!NOTE] > > Hopefully now, regardless of how many more deployments you make from here, your logs will persist! # Autoscale based on metrics Source: https://docs.fly.io/launch/autoscale-by-metric Deploy fly-autoscaler to scale worker Machines on Prometheus queue depth or Temporal workflows, using FAS_ env vars and Expr Machine counts. The metrics-based autoscaler scales an app's Machines based on any metric, such as pending work items or queue depth. Scaling on metrics other than requests or connections is useful for apps, like background workers, that aren't running web services. Apps with services that need to scale based on HTTP requests can use the built-in Fly Proxy [autostop/autostart feature](/launch/autostop-autostart) for Machines. The autoscaler works by collecting metrics from different sources, such as Prometheus or Temporal, and computing the number of required Machines based on those metrics. This reconciliation process happens on a loop every 15 seconds by default. The autoscaler uses the [Expr language](https://expr-lang.org/) for defining target Machine counts, which gives a rich set of built-in arithmetic functions. You run the autoscaler as an app based on the [`fly-autoscaler`](https://github.com/superfly/fly-autoscaler) image. The app runs within your organization so you have full control over it. You can customize the autoscaler to work with your specific scaling needs. ## Quickstart To get up and running, you'll set up the app, configure secrets, set the configuration, and deploy the autoscaler. As a prerequisite, you need an existing target application that you want to scale and a user-defined metric to scale on. In this example, you'll scale on a Prometheus metric called `queue_depth` but you can replace that with your own. You can also [scale based on Temporal workflows](#scale-based-on-pending-temporal-work). ### Create the autoscaler application First, create a new Fly.io app that will run the autoscaler. Replace the `my-autoscaler` name with a unique name for your autoscaler application: ``` fly apps create my-autoscaler ``` ### Create a deploy token The first auth token you'll need is one that has permissions to deploy your target app: ```bash theme={null} fly tokens create deploy -a my-target-app ``` Copy the resulting token and set it as a secret on your autoscaler app: ```bash theme={null} fly secrets set -a my-autoscaler --stage FAS_API_TOKEN="FlyV1 ..." ``` ### Create a token to read from Prometheus The next auth token you'll need is one that has permissions to read from your organization's Prometheus data on Fly: ```bash theme={null} fly tokens create readonly my-org ``` Copy the token and use it as a secret on your autoscaler app: ```bash theme={null} fly secrets set -a my-autoscaler --stage FAS_PROMETHEUS_TOKEN="FlyV1 ..." ``` ### Configure your autoscaler `fly.toml` Next, set up a `fly.toml` configuration file for your autoscaler to set environment variables. Replace the `my-autoscaler`, `my-target-app`, and `my-org` with values for your situation. ```toml theme={null} app = "my-autoscaler" [build] image = "flyio/fly-autoscaler:0.3" [env] FAS_PROMETHEUS_ADDRESS = "https://api.fly.io/prometheus/my-org" FAS_PROMETHEUS_METRIC_NAME = "qdepth" FAS_PROMETHEUS_QUERY = "sum(queue_depth{app='$APP_NAME'})" FAS_APP_NAME = "my-target-app" FAS_CREATED_MACHINE_COUNT = "min(50, qdepth / 2)" [metrics] port = 9090 path = "/metrics" ``` Environment variables are the primary way to define your configuration. Metrics collection settings: * `FAS_PROMETHEUS_ADDRESS` defines the Prometheus URL endpoint to query. * `FAS_PROMETHEUS_METRIC_NAME` defines the local variable name that the metric result will be stored as. This example stores the query result value as `qdepth`. * `FAS_PROMETHEUS_QUERY` defines the Prometheus query to run. This example computes the sum of a user-defined `queue_depth` metric. Autoscaling settings: * `FAS_APP_NAME` is the name of the target application to scale. * `FAS_CREATED_MACHINE_COUNT` defines an [Expr](https://expr-lang.org/) expression to calculate the required number of Machines. The autoscaler creates or destroys Machines to reach the required number. This example expression assumes that each Machine could handle two items in the queue and uses the `min()` function to prevent the autoscaler from scaling more than `50` Machines. ### Deploy the autoscaler The autoscaler only works on a single Machine, so you'll use the `--ha` option to turn off the high availability feature that creates two Machines: ```bash theme={null} fly deploy --ha=false ``` After the autoscaler deploys, you should see the number of Machines in your target application increase as your user-defined `queue_depth` gauge increases. > [!NOTE] > > **Note:** The autoscaler creates new Machines in an application by cloning existing Machines. It will not scale to zero and will always keep at least one Machine running. You can find a full working example of the autoscaler at our [fly-autoscaler-example](https://github.com/fly-apps/fly-autoscaler-example) repo. ## More use cases ### Start and stop instead of create and destroy Machines If you already have a pool of created Machines that you want to autoscale, you can use the `FAS_STARTED_MACHINE_COUNT` expression to stop and start Machines instead of creating and destroying them with `FAS_CREATED_MACHINE_COUNT`. When you use `FAS_STARTED_MACHINE_COUNT`, the autoscaler sends a termination signal to the Machines instead of destroying them when scaling. It will also automatically cap the number of Machines that can be started to the number of pre-created Machines. When you scale by starting and stopping existing Machines, your Machines will start up quickly. You'll pay for the Machine's CPU and RAM when they're running and for the rootfs when they're stopped. When you scale by creating and destroying Machines, your Machines will be slightly slower to reach a `started` state since it takes longer to create a Machine than to start one. You won't need to create a "pool" of Machines. You'll only pay for the Machine's CPU and RAM when they're running and won't need to pay for rootfs since the Machines are destroyed when not needed. ### Scale multiple applications You can scale multiple independent applications with the same autoscaler by using a wildcard expression for your application name. Your applications must all share a common prefix and they must all be in the same organization. To enable multi-app scaling, you will need to use an organization-wide auth token rather than an app-specific deploy token: ```bash theme={null} fly tokens create org my-org ``` and then set the resulting token on your autoscaler application: ```bash theme={null} fly secrets set -a my-autoscaler --stage FAS_API_TOKEN="FlyV1 ..." ``` Next, set the organization name and application wildcard in your `fly.toml` config: ```toml theme={null} [env] FAS_ORG="my-org" FAS_APP_NAME="my-app-*" ``` Then use `$APP_NAME` or `${APP_NAME}` in your Prometheus query to identify the current application being scaled: ```toml theme={null} [env] FAS_PROMETHEUS_QUERY = "sum(queue_depth{app='$APP_NAME'})" ``` You can find a working example of multi-application scaling in the [fly-autoscaler-multiapp-example](https://github.com/fly-apps/fly-autoscaler-multiapp-example) repository. ### Scale based on pending Temporal work The Temporal metrics collector periodically checks for the total number of workflows in a “running” state. By default, it will check every 15 seconds. You can connect to your Temporal namespace using the `FAS_TEMPORAL_` environment variables. For example: ```toml theme={null} [env] FAS_TEMPORAL_ADDRESS = 'mynamespace.lyeth.tmprl.cloud:7233' FAS_TEMPORAL_NAMESPACE = 'mynamespace.lyeth' FAS_TEMPORAL_METRIC_NAME = 'queue_depth' FAS_APP_NAME = "my-target-app" FAS_CREATED_MACHINE_COUNT="workflow_count / 10" ``` In the example above the autoscaler is set up to create or destroy Machines for an app that can handle up to 10 workflows at a time (based on the current workflow count) with: `FAS_CREATED_MACHINE_COUNT="workflow_count / 10`. If you want to ensure you don’t exceed a specific number of Machines, then you can use a `min()` expression to cap it: `FAS_CREATED_MACHINE_COUNT="min(50, workflow_count / 10)`. This ensures that no more than 50 Machines get created, regardless of how many workflows are executing. You'll also need to load the certificate and key data as secrets from your `ca.pem` and `ca.key` files: ```bash theme={null} fly secrets set --stage FAS_TEMPORAL_CERT_DATA="$( [!NOTE] > > **`min_machines_running`:** This setting only maintains the specified minimum number of running Machines in your app's primary region. It has no effect on other regions where your app is deployed. If you need to maintain minimum running Machines in multiple regions, you'll need to manage those separately through other means like [`fly scale count`](/flyctl/cmd/fly_scale_count). ### Maximum number of Machines running There's no "maximum machines running" setting, because the maximum number of Machines is the total number of Machines in your app. Fly Proxy autostop/autostart never creates or destroys Machines for you. The maximum number of running Machines is the number of Machines created for your app on launch, or using `fly scale count` or `fly machine clone`. For example, if you want to have a maximum of 10 Machines available to service requests, then you need to create 10 Machines for your app. Learn more about [scaling the number of Machines](/launch/scale-count). ### Keep all Machines running continuously If you need all your app's Machines to run continuously, then you can set `auto_stop_machines` to `"off"` and `auto_start_machines` to `false` to turn off autostop/autostart completely. If you only need a certain number of your app's Machines to run continuously, then you can set `auto_stop_machines` to `"suspend"` or `"stop"` and `min_machines_running` to `1` or higher. Note that `min_machines_running` only applies to your app's primary region. ### Private apps Requests to apps without services configured on your private network don't get routed through Fly Proxy and so Machines can't be automatically stopped or started by Fly Proxy. To use Fly Proxy autostart/autostart for private apps, set up services and use a Flycast private IPv6 address. See [Flycast - Private Fly Proxy Services](/networking/flycast) and [Autostop/autostart private apps](/blueprints/autostart-internal-apps). ### Apps that shut down when idle Setting your app to automatically stop or suspend Machines when there's excess capacity using `auto_stop_machines` can be a substitute for when your app doesn't shut itself down automatically after a period of inactivity. If your app already shuts down when idle, then you can set `auto_start_machines = true` and `auto_stop_machines = "off"` to have Fly Proxy automatically restart the Machines stopped by your app. If you want a custom shutdown process for your app, then you can code your app to exit when idle. Here are some examples: * [Shutting Down a Phoenix App When Idle](https://fly.io/phoenix-files/shut-down-idle-phoenix-app/): a post by Chris McCord on adding a task to an Elixir app's supervision tree that shuts down the Erlang runtime when there are no active connections. * For Rails apps, the `dockerfile-rails` generator provides a [--max-idle](https://github.com/rubys/dockerfile-rails#addremove-a-feature) option that exits after *n* seconds of inactivity. * [A Tired Proxy in Go](https://github.com/superfly/tired-proxy) used in [Building an In-Browser IDE the Hard Way](https://fly.io/blog/remote-ide-machines/). [There's a community fork with more recent updates](https://community.fly.io/t/improved-tired-proxy-for-use-with-fly-machines/10584). * A minimal demo app in TypeScript/Remix: [code](https://github.com/fly-apps/autoscale-to-zero-demo) & [demo](https://autoscale-to-zero-demo.fly.dev/). Fly Postgres also [supports scaling to zero](https://community.fly.io/t/scale-to-zero-postgres-for-hobby-projects/12212). ## Default settings The defaults differ for new apps created with `fly launch` and for apps with no settings configured for autostop/autostart. ### Apps created with `fly launch` When you create a new app using the `fly launch` command, `fly.toml` uses the [scale to zero](#scale-to-zero) settings. ### Apps with no autostop/autostart settings For apps that don't explicitly specify any autostop/autostart settings in `fly.toml`, Fly Proxy will automatically start stopped Machines when needed, but won't automatically stop or suspend them. ## Related topics * [How Fly Proxy autostop/autostart works](/reference/fly-proxy-autostop-autostart) * [Autostop/autostart private apps](/blueprints/autostart-internal-apps) * [Autoscale based on metrics](/launch/autoscale-by-metric) # Continuous Deployment with Fly.io and GitHub Actions Source: https://docs.fly.io/launch/continuous-deployment-with-github-actions Deploy to Fly.io automatically on every push with GitHub Actions: create a deploy token, store it as FLY_API_TOKEN, and add a workflow that runs flyctl deploy. ![A man writing code on a vintage desktop computer](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/continuous-deployment.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=04f20e8735b2cf8736a1e85e8456e638) This guide works through setting up your app for continuous deployment to Fly.io from the app's GitHub repository. You might also like our blueprint on [deploying review apps with GitHub Actions](/blueprints/review-apps-guide). You'll start with an example application called [go-example](https://github.com/fly-apps/go-example). It's a simple, lightweight app that displays the Fly.io region that served the request. The first section is a speed-run through the steps to make the go-example app automatically deploy to Fly.io from GitHub. The next section loops back for a [longer look at each step](#a-longer-look-at-the-deployment-process). ## Speed-run your way to continuous deployment 1. Fork the [go-example](https://github.com/fly-apps/go-example) repository to your GitHub account. 2. Clone the new repository to your local machine. 3. Run `fly launch --no-deploy` from within the project source directory to create a new app and a `fly.toml` configuration file. 4. Type `y` to when prompted to tweak settings and enter a name for the app. Adjust other settings, such as region, as needed. Then click **Confirm Settings**. 5. Still in the project source directory, get a Fly API deploy token by running `fly tokens create deploy -x 999999h`. Copy the output, including the `FlyV1` and space at the beginning. 6. Go to your newly-created repository on GitHub and select **Settings**. 7. Under **Secrets and variables**, select **Actions**, and then create a new repository secret called `FLY_API_TOKEN` with the value of the token from step 5. 8. Back in your project source directory, create `.github/workflows/fly.yml` with these contents: ```yaml theme={null} name: Fly Deploy on: push: branches: - master # change to main if needed jobs: deploy: name: Deploy app runs-on: ubuntu-latest concurrency: deploy-group # optional: ensure only one action runs at a time 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 }} ``` > [!NOTE] > > **Note:** The `go-example`’s default branch is `master`. If you’re using a different app, yours might be `main`. Change the `branches` value in the `fly.yml` file accordingly. 9. Commit your changes and push them up to GitHub. You should be pushing two new files: `fly.toml`, the [Fly Launch](/launch/index) configuration file, and `fly.yml`, the GitHub action file. Then the magic happens - The push triggers a deploy, and from now on whenever you push a change, the app will automatically be redeployed. If you want to watch the process take place, head to the repository and select the **Actions** tab, where you can view live logs of the commands running. ## A longer look at the deployment process ### `fly.toml` and the repository **Step 1** is a simple GitHub Fork; there's not a lot to say about that except that you need to do it, because you want control of the repository that you're deploying from. **Step 2** is just cloning the repository to your local system so that you can edit and push changes to it. **Steps 3 and 4** create a new app on Fly.io and a `fly.toml` configuration file to go into the repository. > [!NOTE] > > A note about `fly.toml` in repositories: Usually, when Fly.io ships examples, we avoid putting the `fly.toml` file in the repository by including `fly.toml` in the `.gitignore` file. And users should be creating their own `fly.toml` with the `fly launch` command. When using GitHub Actions though, you want your `fly.toml` in the repository so that the action can use it in the deployment process. ### API tokens **Step 5** is about getting an API token. You can generate a deploy token to use to authorize a specific application. That's what `flyctl tokens create deploy -x 999999h` gives you. Remember to copy the whole token from the output, including the `FlyV1` and space at the beginning. For a token that can deploy every app in an organization, create an org-scoped token with `fly tokens create org`. See [Access tokens](/security/tokens) for the options. **Steps 6 and 7** make your new token available to GitHub Actions that run against your repository. You'll add the token as a secret in the repository's settings. Under the **Settings** tab, go to **Secrets and variables** and select **Actions**. Click on the green "New repository secret" button, enter the name as `FLY_API_TOKEN`, and copy the token as the secret. If you'd prefer an environment secret instead, then you need to list the environment you selected in your deploy step. For example: ```yaml theme={null} deploy: name: Deploy app runs-on: ubuntu-latest environment: production ``` ### Building the workflow and deployment **Step 8** is the heart of the process, where you put in place a workflow. Now, GitHub has a UI which allows you to select and edit workflows, but you can also modify them as part of the repository. So you create `.github/workflows/fly.yml` - you'll likely want to `mkdir -p .github/workflows` to quickly create the directories - and load up the file with a GitHub Action recipe. GitHub Action recipe, line by line: ```yaml theme={null} name: Fly Deploy ``` This sets the displayed name for the action. ```yaml theme={null} on: push: branches: - master ``` When should this action be run. There's lots of options but in this case, in response to any `push` to the repository's `master` branch. If your repository uses a default branch other than `master`, such as `main`, then you should change that here. ```yaml theme={null} jobs: deploy: name: Deploy app runs-on: ubuntu-latest concurrency: deploy-group ``` An action is made up of named jobs, in this case one job to deploy the application. The jobs run on a virtual machine. This section gives the "deploy" job the name "Deploy app" and tells GitHub Actions to run it on a virtual machine with the latest version of Ubuntu on it. Optionally, use the `concurrency` key with a custom group name to ensure that only one action runs at a time for that group. The next part is to set up the steps needed to complete this job. ```yaml theme={null} steps: - uses: actions/checkout@v4 ``` The first step is one of the built in Actions steps. The step `uses` the [`checkout@v4` action](https://github.com/marketplace/actions/checkout) which checks out the repository into a directory on the virtual machine. You're now ready to deploy. ```yaml theme={null} - uses: superfly/flyctl-actions/setup-flyctl@master - run: flyctl deploy --remote-only ``` This step `uses` the [superfly/flyctl-actions action](https://github.com/marketplace/actions/github-action-for-flyctl). This is a GitHub action created by Fly.io which wraps around the `flyctl` command. The wrapper is invoked with the `deploy` argument which will take over the process of building and moving the application to the Fly.io infrastructure. It uses the settings from the `fly.toml` file to guide it and uses the `FLY_API_TOKEN` to authorize its access to the Fly.io GraphQL API. ```yaml theme={null} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} ``` This pulls the API token from GitHub's secrets engine and puts it into the environmental variables passed to the action. **Step 9** pushes your two new files to the repository: `fly.toml`, the app configuration file, and `fly.yml`, the GitHub action file. The push triggers your first automatic deploy. The GitHub action now triggers a redeploy each time you push changes to your repo. ## Conclusion and further reading And that's the deployment process. You can, of course, leverage the GitHub Actions environment to manage more intricate deployments, interact with other applications—say to send Slack messages on completion—or whatever else you can dream up. **Read:** * [Blueprint: Deploy review apps with GitHub Actions](/blueprints/review-apps-guide) * [Deploy Tokens](/security/tokens) * [GitHub Actions Documentation](https://docs.github.com/en/actions) **See:** * [GitHub Actions for flyctl](https://github.com/superfly/flyctl-actions) # Create an app with Fly Launch Source: https://docs.fly.io/launch/create Create an app on Fly.io with fly launch: when it generates a Dockerfile, how framework scanners work, the --image and --dockerfile options, and next steps. ![Illustration by Annie Ruygt of a figure running and carrying a balloon](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/launch-create.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=26543e514eae4fa12d105c04959db278) This guide goes into some detail about using Fly Launch to create and configure an app. See [Getting Started](/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. ## Create an app with Fly Launch To create a brand new app on Fly.io, run this command from the source directory of your project: ```bash theme={null} fly launch ``` If you're in a hurry, you can also try our super-simple [demo app](/getting-started/launch-demo). Or get started with your favorite [language or framework](/getting-started/get-started-by-framework). ## Does `fly launch` create a Dockerfile? Yes. For most languages and frameworks, `fly launch` generates a Dockerfile for your app and writes it to your project directory, usually along with a `.dockerignore` file. You don't need to write a Dockerfile before you launch. The generated Dockerfile is yours. Edit it to suit your app and commit it with the rest of your source code. `fly deploy` builds your app's image from it. | Your project | What `fly launch` does | | - | - | | No Dockerfile, supported language or framework | Generates a Dockerfile | | Already has a Dockerfile | Usually uses your Dockerfile. See [Point to an image or use a Dockerfile to build](#point-to-an-image-or-use-a-dockerfile-to-build). | | `--image` option | Deploys that image, no Dockerfile needed | | Elixir without Phoenix, or Python without Django, Flask, FastAPI, or Streamlit | Uses [buildpacks](/reference/builders#buildpacks) instead of a Dockerfile | > [!NOTE] > > If you're scripting a launch or using a coding agent, run `fly launch` first and then edit the generated Dockerfile. Writing a Dockerfile from scratch first isn't necessary, and it can stop `fly launch` from generating one that fits your framework. ## Ingredients for a successful `fly launch` The components of a successful launch, ready for the first deployment: * A way to get a Docker image that we can use to get your app running on a Machine. Usually that's a Dockerfile, which `fly launch` can generate for you. * A `fly.toml` file, created by `fly launch` or provided by you, for your app's Fly.io-specific [configuration](/reference/configuration). * Optional resources such as [public IP addresses](/networking/services), [app secrets](/apps/secrets), and [databases and storage](/database-storage-guides) from Fly.io or extension partners. ## Framework launch scanners Depending on your project, `fly launch` may be able to look at your app's source code and get through that [ingredient list](#ingredients-for-a-successful-fly-launch), straight to a ready-to-deploy Fly App. This is most likely to work for frameworks on which Fly.io has people specializing full time. Right now that's [Elixir/Phoenix](/elixir), [Laravel](/laravel), [Rails](/rails), and [Django](/django), but we also have launch guides for other [languages and frameworks](/languages-and-frameworks). Most scanners generate a Dockerfile for your app, from a built-in template or by running a framework's own generator, such as `dockerfile-rails` for Rails, `@flydotio/dockerfile` for JavaScript frameworks, or `mix phx.gen.release --docker` for Phoenix. A couple of older scanners still use [buildpacks](/reference/builders#buildpacks), which tend to be slow and brittle. Running `fly launch` in a directory containing a working Django app (it happens to be the one from [our Django getting-started example](/django/getting-started)): ```bash cmd theme={null} fly launch ``` ```out out theme={null} Scanning source code Detected a Django app Creating app in /flyio/hello-django We're about to launch your Django app on Fly.io. Here's what you're getting: Organization: MyName (fly launch defaults to the personal org) Name: hello-django (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) ? Do you want to tweak these settings before proceeding? Yes ... ``` The flyctl Django scanner has taken ownership of the launch. You can tweak the basic settings on the Fly Launch web page and then run `fly deploy` to deploy the new app. Visit our [Django guide](/django/getting-started) to see how that story will end. (Spoiler: it has a happy ending.) ## Custom launch You can nudge `fly launch` to better suit your project. ### Point to an image or use a Dockerfile to build Tell `fly launch` how you want to get the Docker image for your app, using either the `--image` or `--dockerfile` option, or by putting a [Dockerfile](/languages-and-frameworks/dockerfile) in your project source directory. If your project already has a Dockerfile, what `fly launch` does with it depends on what else it detects: * **Rails, Phoenix, JavaScript framework, and Laravel (PHP 8.1 or later) apps:** The framework scanner still configures your app, but keeps your Dockerfile and skips generating one. * **Django and RedwoodJS apps, and Laravel apps on older PHP versions:** The framework scanner offers to replace your Dockerfile with a generated one. It keeps yours if you decline or if `fly launch` runs non-interactively, and replaces it if you pass `--yes`. * **Everything else:** The Dockerfile scanner takes over. It doesn't do a lot of configuration, but it uses your Dockerfile as is and prevents the other scanners from taking over. The actual Docker image build (or image pull) for a Fly App takes place during deployment. `fly launch` sets the stage by recording how to build, or get, the image, and both the first and all later deploys use that information. ### Customize the configuration file You can provide your own `fly.toml` and `fly launch` will offer to copy that configuration to a new app. `fly.toml` sets a starting point for the app configuration, and in some cases a framework launch scanner might overwrite parts of it. The `fly launch` command has plenty of [options](/flyctl/cmd/fly_launch) you can use to control how your app gets created and provisioned. If `fly launch` doesn't have a scanner that can set up your app automatically, it will still initialize a new Fly App in your Fly.io organization and provide you with a default app configuration that's a reasonable starting point for a simple web app. You'll need to ensure that your Fly App's name is unique across all of Fly.io. By default, Fly Launch will derive an app name from the current directory name. If this is something common like "hello-world", then there's a good chance your launch will fail. You can use the `--name` flag to specify a unique name up front. You can also perform an entirely manual "launch", skipping all the launch scanners and full-service resource provisioning, using `fly apps create`, a hand-crafted (or copied) `fly.toml`, and step-by-step resource provisioning, followed by `fly deploy`. ## After `fly launch` If you've run `fly launch` but haven't deployed yet (hint: you can do this with `fly launch --no-deploy`), or you deployed but want to make changes, then you can: * change your configuration * update your app source * change or provision platform resources such as [public IP addresses](/networking/services), [app secrets](/apps/secrets), and [databases and storage](/database-storage-guides). And then deploy (or redeploy) with [`fly deploy`](/launch/deploy). ## Grow and scale Check out some of the ways you can increase availability, capacity, and performance with Fly.io: * Follow the blueprint for [extra Machines for more resilient apps](/blueprints/resilient-apps-multiple-machines) * Read up on [App availability and resiliency](/apps/app-availability) * [Autoscale Machines based on load or custom metrics](/reference/autoscaling) * [Scale Machine CPU and RAM](/launch/scale-machine) * [Scale Machine count](/launch/scale-count) # Deploy an app Source: https://docs.fly.io/launch/deploy Run fly deploy to build and release your app on Fly.io Machines. See how the image is chosen, deploy strategies, release commands and smoke checks. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-apps.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=2adbf4330aef8a0716f37a731d3556c5) A feature of Fly Launch, the `fly deploy` flyctl command builds your app and spins it up on one or more Fly Machines, applying the configuration specified in a local `fly.toml` file. Whether you've run [`fly launch`](/launch/create) with the `--no-deploy` option, or want to apply changes to your deployed app's source or configuration, you can make a new release by running ```bash theme={null} fly deploy ``` from the source directory of your project, where any app source code, project config, `fly.toml`, and Dockerfile are located. If you haven't deployed the app before, `fly deploy` creates one or two Machines for every [process group](/launch/processes) defined in the app's `fly.toml` file, depending on whether there are services and volumes configured. If you haven't explicitly defined any process groups, the first deployment gives you two Machines for redundancy. For more information about when Fly Launch creates one Machine or two Machines, refer to [Redundancy by default on first deploy](/apps/app-availability#redundancy-by-default-on-first-deploy). Subsequent deployments update the app's Machines as a group, with the latest local changes to the app's source and configuration. It's possible for a Fly App to have standalone Machines that aren't managed by `fly deploy`. ## Configuration flyctl will look for a `fly.toml` file to find the name of the app to operate on, and for an app configuration to apply during deployment. Use `fly deploy -a ` to override the app name given in `fly.toml` and deploy the project in the current working directory to a different existing Fly App. There are [a number of other options](/flyctl/cmd/fly_deploy) you can use with the `fly deploy` command. ## The build `fly deploy` builds, or gets, the Docker image for the app, and refreshes Machines with the latest changes. Here's how `fly deploy` determines how to get the app's Docker image: 1. If an image is specified, either with the `--image` option or in the `[build]` section of `fly.toml`, use that image, regardless of the presence of a Dockerfile in the working directory or the use of the `--dockerfile` option. 2. Otherwise, check the [`[build]` section of `fly.toml`](/reference/configuration#the-build-section) and use the method specified there, whether it's a Dockerfile or a buildpack (but don't use buildpacks if you don't have to; they're brittle, bloated, and prone to change). 3. Otherwise, if the `--dockerfile` flag supplies a path to a Dockerfile, use that Dockerfile to build the image. You read that right. The `--dockerfile` flag is looked at *after* the `[build]` section of the config file. 4. Otherwise, if there's a `Dockerfile` (named exactly `Dockerfile` or `dockerfile`) in the local working directory, use that Dockerfile for the build. ## IP addresses If the app to be deployed is configured with an eligible HTTP `[[services]]` section, and it does not yet have [public IP addresses](/networking/services), `fly deploy` provisions a public IPv6 and a shared public IPv4 Anycast address. ## Machines not managed by Fly Launch Machines created using `fly deploy` (or as part of a deployment during `fly launch`), or by `fly clone`ing such a Machine, carry a piece of metadata marking them as belonging to Fly Launch (`"fly_platform_version": "v2"`). These Machines are updated as a group on all subsequent `fly deploy` commands. New Machines created using `fly machine run`, also known as unmanaged Machines, don't have the `fly_platform_version` metadata, and are not automatically managed by Fly Launch. These unmanaged Machines can have their own configuration different from that of the App, and can even be based on a different Docker image. ## Volume mounts and `fly deploy` If a Machine has a mounted [volume](/volumes), `fly deploy` can't be used to mount a different one. You can change the mount point at which the volume's data is available in the Machine's file system, though. This is configured in the [`[mounts]` section](/reference/configuration#the-mounts-section) of `fly.toml`. Learn more about [using Fly Volumes](/volumes). ## `fly deploy` configuration in `fly.toml` Configure the following deployment behavior in the [`[deploy]` section](/reference/configuration#the-deploy-section) of `fly.toml`. ### Release commands You can run a one-off [release command](/reference/configuration#run-one-off-commands-before-releasing-a-deployment) in a temporary VM—using the successfully built release—before that release is deployed. This is good for, e.g., running database migrations. Keep in mind that these temporary VMs do not have access to any volumes you may have mounted in your apps. ### Deployment strategy You can specify one of the following deployment strategies: * `rolling` (default): wait for each Machine to be successfully deployed before starting the update of the next one * `immediate`: bring all Machines down for update at once * `canary`: boot a single new Machine, verify its health, and then proceed with a rolling restart strategy * `bluegreen`: boot a new Machine alongside each running Machine in the same region, and migrate traffic to the new Machines only once all the new Machines pass health checks Learn more about [setting a deployment strategy](/reference/configuration#picking-a-deployment-strategy). ```bash cmd theme={null} fly deploy --strategy canary ``` ```out out theme={null} ... ==> Building image Searching for image 'flyio/hellofly:latest' remotely... image found: img_z1nr0lpjz9v5q98w Watch your app at https://fly.io/apps/aged-water-8803/monitoring Creating canary machine for group app Machine 328745da023e85 [app] update finished: success Canary machines successfully created and healthy, destroying before continuing machine 328745da023e85 was found and is currently in created state, attempting to destroy... Updating existing machines in 'example-app-8803' with canary strategy [1/2] Machine 3287457df77785 [app] update finished: success [2/2] Machine 91857266c41638 [app] update finished: success Finished deploying ... ``` ## Not all changes require a new App release You can [add an IP address](/networking/services#anycast-ip-addresses) to an App, for example, without redeploying. Adding a [secret](/apps/secrets) to the App does require a restart, so `fly secrets set` triggers a new deployment. ## Smoke checks Smoke checks monitor Machines during deployment, for about 10 seconds after a Machine enters the started state, to make sure the Machine stays running. If a Machine is constantly restarting with a non-zero exit code during this time, then the smoke check fails and the deployment is stopped. If possible, the smoke check failure output includes an excerpt of the logs to help you diagnose the issue with your app. ## Related topics * [Create a new app with Fly Launch](/launch/create) * [Troubleshooting your deployment](/getting-started/troubleshooting) # Fly Launch Source: https://docs.fly.io/launch/index Create, configure, deploy, and scale apps with Fly Launch on Fly.io using fly launch, fly.toml, fly deploy, and fly scale, plus autostop and GitHub Actions. Fly Launch helps you configure and orchestrate your app. If you followed one of our [Getting started](/getting-started) guides, then you're already using Fly Launch. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-island.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=a9d1f9a68df2f69c974998489275d873) Fly Launch includes: * the `fly launch` command to get started with a new app * the `fly.toml` file for app configuration * the `fly deploy` command to deploy or redeploy your app * the `fly scale` command for quick horizontal and vertical scaling Learn how to work with Fly Launch: **Create and configure** * [Create an app with Fly Launch](/launch/create) * [Deploy an app](/launch/deploy) * [App configuration reference (fly.toml)](/reference/configuration) * [Add Volume storage](/launch/volume-storage) * [Use process groups for multiple processes in one app](/launch/processes) **Scale and control** * [Scale Machine CPU and RAM](/launch/scale-machine) * [Scale the number of Machines in an app](/launch/scale-count) * [Autostop/autostart Machines](/launch/autostop-autostart) * [Autoscale Machines based on metrics](/launch/autoscale-by-metric) **Custom deployments** * [Deploy an app with GitHub Actions](/launch/continuous-deployment-with-github-actions) * [Deploy review apps with GitHub Actions (Blueprint)](/blueprints/review-apps-guide) * [Deploy monorepo and multi-environment apps](/launch/monorepo) # Monorepo and multi-environment deployments Source: https://docs.fly.io/launch/monorepo Deploy several apps from a monorepo on Fly.io: set the working directory, point fly deploy at a fly.toml with --config, a Dockerfile, and a --build-target. By default, `fly deploy` builds and deploys a `fly.toml` file, a `Dockerfile`, and source code from the current working directory. This is sufficient for deploying a single app, but you can also configure flyctl to build and deploy multiple apps from a monorepo or deploy an app to multiple targets. ## Paths ### Working directory The first argument to `fly deploy` is the path to the working directory for your app's source code. For Dockerfile and Buildpack builds, this is the [build context](https://docs.docker.com/engine/reference/commandline/build/#usage) sent to the Docker daemon. It defaults to the current working directory. You can override this by providing a path: ```bash theme={null} fly deploy ./path/to/app ``` ### `fly.toml` path By default, `fly deploy` will look for a `fly.toml` in the working directory. You can override this using the `--config` option. ```bash theme={null} fly deploy --config ./path/to/fly.toml ``` ### `Dockerfile` path By default, `fly deploy` will look for a `Dockerfile` in the working directory. You can override this using the `--dockerfile` option. ```bash theme={null} fly deploy --dockerfile ./path/to/Dockerfile ``` ## Multi-stage Build Target By default, the final stage of a [multi-stage dockerfile](https://docs.docker.com/develop/develop-images/multistage-build) is exported as the deployed image. You can stop at a specific stage by using the `--build-target` option: ``` fly deploy --build-target web fly deploy --build-target api fly deploy --build-target worker ``` ## Combining options When you specify a working directory, the `--config` and `--dockerfile` paths are relative to the working directory (build context), not your current directory. For example, given this structure: ``` repo/ ├── flyio/ │ └── my-app.toml └── my-app/ └── Dockerfile ``` This won't work, because `./flyio/my-app.toml` doesn't exist inside `./my-app`: ```bash theme={null} fly deploy ./my-app --config ./flyio/my-app.toml ``` Instead, use a path relative to the working directory: ```bash theme={null} fly deploy ./my-app --config ../flyio/my-app.toml ``` ## Examples **Use a different fly.toml file per environment** ``` fly deploy --config ./fly.production.toml fly deploy --config ./fly.staging.toml ``` **Use a different Dockerfile per environment** ``` fly deploy --dockerfile ./Dockerfile.production fly deploy --dockerfile ./Dockerfile.staging ``` **Deploy a subdirectory** ```bash theme={null} fly deploy ./apps/api ``` **Share a multi-stage Dockerfile with several Fly Apps** ```bash theme={null} fly deploy --config fly.api.toml --build-target api ``` # Run multiple process groups in an app Source: https://docs.fly.io/launch/processes Run several programs in one Fly.io app with process groups in fly.toml, attach services to a group, and scale each group with fly scale or fly machine clone. Process groups are a way to configure a single Fly App to run multiple different programs. You define processes in your `fly.toml`, giving each process group a name and a command to run at boot. Every defined process runs in its own Fly Machine(s) within the app, which means they don't compete for resources, and they can be scaled by process group. ## The default process group If you don't explicitly define any processes, then the Machines in a Fly App belong to the default `app` process group, and on boot they run whatever entrypoint process the app's Docker image has. (Apps are shipped to Fly.io in Docker images, even though [we run VMs, not containers](https://fly.io/blog/docker-without-docker/).) When the `fly launch` command creates a `fly.toml` file for your app, the default service defined in the `http_service` section belongs to the `app` process group. ## Run multiple processes To run multiple processes, first make sure all the things you want to run are installed in your app's Docker image. Define a [`processes` section](/reference/configuration#the-processes-section) in your app's `fly.toml`, pairing process group names with the commands they should run. Here's an example: ```toml theme={null} [processes] web = "bin/rails fly:server" cron = "supercronic /app/crontab" ``` Once there's a `[processes]` section in your config, flyctl assumes this is a complete list of your processes. If you want an `app` process group alongside others, add it to the config explicitly. Process group commands in a Fly App correspond to CMD in Docker; they don't replace the ENTRYPOINT of your app image, but will supersede CMD. ## Processes and services Chances are, you only want user requests to hit one of your processes; in the above example, that process is `web`, so you would specify the `web` process in the `http_service` or in the [`[[services]]` section](/reference/configuration#the-services-sections) for the app's HTTP service: ```toml theme={null} ... [[services]] http_checks = [] internal_port = 8080 processes = ["web"] ... ``` You can define distinct services for each process that needs to accept connections from Fly Proxy (whether [publicly](/networking/services) or via [Flycast](/networking/flycast)), by creating multiple `[[services]]` sections in `fly.toml`. > [!NOTE] > > **Important:** Make sure processes handle connections on different [external ports](/reference/configuration#services-ports). Fly Proxy doesn't know about process groups; it load-balances requests among all Machines with a service configured on the requested port. ## Deployment `fly deploy` creates at least one Machine for each process group, and destroys all the Machines that belong to any process group that isn't defined, in your app's `fly.toml` file. It also updates the command, services, and health checks for each `fly deploy`-managed Machine on the app; and creates a new app release. So on the first deployment (either at the end of `fly launch` or at the first explicit `fly deploy`), flyctl creates and starts at least one Machine for each process group in `fly.toml`. If you add new process groups in an app's `[processes]` block, then the next `fly deploy` spins up at least one new Machine to run each new process. If you remove any process groups from an app's `[processes]` block, then the next `fly deploy` destroys the Machines that belong to the deleted process. For more information about how many Machines are created by `fly launch` and `fly deploy`, refer to [App Availability and Resiliency](/apps/app-availability). ## Scale a process group horizontally There are two ways to scale the number of Machines in an app. This section provides a summary of horizontal scaling. For more information, refer to [Scale the Number of Machines](/launch/scale-count#scale-by-process-group). ### Scale a process group horizontally with `fly scale count` You can scale the number of Machines (up or down) per process group with the `fly scale count` command. The following example scales the `web` process group to 8 Machines, and the `cron` process group to 2 Machines: ``` fly scale count web=8 cron=2 ``` You can scale by [region](/reference/regions) with the `--region` option. Specify multiple regions and the Machines will be distributed as evenly as possible between them. The following command would create 2 Machines in `gru` and 2 Machines in `bog`: ```bash theme={null} fly scale count web=4 --region gru,bog ``` ### Scale a process group horizontally with `fly machine clone` You can also use the `fly machine clone` and `fly machine destroy` commands to scale the number of Machines individually within a process group. When you clone a Machine that belongs to a process group, the new Machine is created with the command, services, and checks that are configured on the app for that process. You can add a [region](/reference/regions) to your app by cloning a Machine into a new region. First, run `fly status` to get a list of Machines with IDs and process groups. The following example clones a Machine into the `gru` (São Paulo) region: ``` fly machine clone --region gru e2865641be9786 ``` Run the following command to force stop and destroy a Machine: ``` fly machine destroy e2865641be9786 --force ``` ## Scale a process group vertically You can scale Machine CPU and memory settings for an entire process group using the `fly scale vm` and `fly scale memory` commands. This section provides a summary of vertical scaling. For more information, refer to [Scale Machine CPU and RAM](/launch/scale-machine#scale-by-process-group). > [!NOTE] > > **Note**: The `--process-group` option is aliased to `-g` for faster command entry. The following example changes the CPU/RAM of Machines in the `web` process group to a different preset combination: ``` fly scale vm performance-2x --process-group web ``` Run `fly platform vm-sizes` for a list of the valid CPU/RAM preset combinations. This example changes only the RAM of Machines in the `web` process group: ``` fly scale memory 4096 --process-group web ``` ## Move a Machine between process groups You can change the process group of an existing Machine by updating its metadata: ```bash theme={null} fly machine update --metadata fly_process_group=app 21781973f03e89 ``` Then deploy the app to apply the configuration for that process group to the Machine: ```bash theme={null} fly deploy ``` # Scale the Number of Machines Source: https://docs.fly.io/launch/scale-count Scale a Fly.io app horizontally with fly scale count or by cloning and destroying Machines, across regions and process groups, apps with volumes, or to zero. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-drones.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=62c23d247fdc2ceda7de9a74f2bc6a54) The maximum capacity of an app is determined by the number of Fly Machines belonging to it. You scale an app horizontally by creating or destroying Machines. > [!NOTE] > > Starting and stopping existing Machines is much faster than creating and destroying them. Stopped Machines are cheaper than running ones. Machines release their CPU and RAM when they stop, and their rootfs is rebuilt fresh from their Docker image, ready to start. > > For bursty workloads, we recommend creating enough Machines to handle your peak load, and adjusting the active capacity of the app by stopping and starting Machines as needed. > > For more information, see: > > * [Automatically Stop and Start App Machines](/launch/autostop-autostart), for adjusting active capacity based on traffic. > * [Autoscale based on metrics](/launch/autoscale-by-metric), to use the autoscaler app to scale Machines based on any metric. > * [`fly machine` commands](/flyctl/cmd/fly_machine), including [`start`](/flyctl/cmd/fly_machine_start) and [`stop`](/flyctl/cmd/fly_machine_start) subcommands to target individual Machines with flyctl. > * Machines API [stop](/machines/api/machines-resource#stop-a-machine) and [start](/machines/api/machines-resource#start-a-machine) endpoints. There are two ways to change the number of Machines managed by [Fly Launch](/launch/index) after deploying an app for the first time: 1. with the `fly scale count` subcommand 2. by explicitly [cloning](#scale-up-with-fly-machine-clone) or [destroying](#scale-down-with-fly-machine-destroy) existing Machines on the app `fly scale count` uses internal rules to create or destroy Machines to reach the target scale that you specify. Machines get created when the target number of Machines is higher than the existing total, and Machines get destroyed when the target number of Machines is lower than the existing total. When Machines are created or destroyed using `fly scale count` or `fly machine clone`/`fly machine destroy`, the resulting scale is preserved by `fly deploy`—except in the case that you scale right down to zero Machines. If there are no existing Machines, then `fly deploy` seeds the app with new Machines in the `primary_region` and according to the `[processes]` configured in your `fly.toml` file. For more information about how many Machines are created when you launch a new app or deploy from zero, refer to [Redundancy by default on first deploy](/apps/app-availability#redundancy-by-default-on-first-deploy). ## View the app's current scale `fly scale show` outputs all the scale information about an app's Machines: how many Machines of each VM specification, in each of the app's process groups, in each [region](/reference/regions). Here's an example to illustrate that: ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: my-app-name Groups NAME COUNT KIND CPUS MEMORY REGIONS app 3 shared 4 1024 MB mia,scl(2) disk 3 shared 4 1024 MB ord(2),scl task 4 performance 1 2048 MB iad,mia,ord,scl ``` This app has 10 Machines total. The `app` process group has two in scl (Santiago) and one in mia (Miami). `disk` has two in ord (Chicago) and one in scl. `task` has one Machine in each of iad (Ashburn), mia, ord, and scl. You can put your Machines wherever works best for your app. ## Scale the number of Machines in a single region The following examples apply to a Fly App's default process group: the `app` process group. If you don't define [process groups](/launch/processes), then all of the app's Machines belong to the `app` process group, with the exception of any Machines created with `fly machine` commands or the Machines API. `fly scale count` applies changes to the default process group if it is not [passed explicit per-process target counts](#scale-by-process-group). Here's the `fly scale show` output for a newly deployed web app, with two machines in yyz (Toronto): ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: my-app-name Groups NAME COUNT KIND CPUS MEMORY REGIONS app 2 shared 1 256 MB yyz(2) ``` The simplest way to scale up is by using `fly scale count `. This command just adds more Machines to the default process group in the app's existing region: ```bash theme={null} fly scale count 4 ``` The result: ``` Groups NAME COUNT KIND CPUS MEMORY REGIONS app 4 shared 1 256 MB yyz(4) ``` Now there are four Machines in yyz. ## Scale an app with volumes When you scale an app with volumes, the `fly scale count` command attaches volumes to the Machines it creates: first using unattached volumes that already exist in the Machine's region, and then creating new volumes if there are not enough—or any—existing volumes. When the `fly scale count` command destroys Machines, the volumes become unattached and can be used again when scaling up. > [!WARNING] > > The `fly scale count` command creates new empty volumes, or attaches existing volumes, and does not copy or move any data between volumes. In this example, the app starts with 1 Machine with an attached volume in the `disk` process and 1 unattached volume, all in the yul region. The following command to scale the `disk` process to 3 Machines creates 2 new Machines and 1 new volume: ```bash cmd theme={null} fly scale count disk=3 --region yul ``` ```output output theme={null} App 'my-app-name' is going to be scaled according to this plan: +2 machines for group 'disk' on region 'yul' with size 'shared-cpu-1x' +1 new volumes and using 1 existing volumes for group 'disk' in region 'yul' ? Scale app my-app-name? (y/N) ``` Note that the preceding example specifies: * The region where we have unattached volumes and want the Machines to live. * The process group, because our `my-app-name` app has volumes for Machines that belong to the `disk` process group. ## Scale an app's regions The `--region` option takes one or more [region codes](/reference/regions). `fly scale count` creates and/or destroys Machines to reach the specified target count across the regions you list in this option, and tries to balance its changes across these regions. For example, to end up with a total of three Machines between the yyz (Toronto) and ewr (Secaucus) regions on an app: ```bash theme={null} fly scale count 3 --region yyz,ewr ``` In the above example, the resulting total Machine count seen with `fly scale show` will be more than 3 if the app has existing Machines in regions other than yyz and ewr: ``` Groups NAME COUNT KIND CPUS MEMORY REGIONS web 4 shared 1 256 MB ewr,mia,yyz(2) ``` You can also set the scale explicitly per region by specifying a single region per `fly scale` command. For example: ```bash theme={null} fly scale count 4 --region ewr ``` If `--region` is not used, then the target count is distributed across all the regions in which the app already has Machines that belong to any process group. ## Scale by process group `fly scale count` takes explicit target counts per process group. Here's an app with two process groups running in two regions, nrt (Tokyo) and yyz (Toronto): ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: my-app-name Groups NAME COUNT KIND CPUS MEMORY REGIONS web 4 shared 1 256 MB nrt(2),yyz(2) worker 4 shared 1 256 MB nrt(2),yyz(2) ``` Scale it out to 10 `web` Machines and 6 `worker` Machines, without constraining the change by region: ```bash cmd theme={null} fly scale count web=10 worker=6 ``` ```out out theme={null} App 'my-app-name' is going to be scaled according to this plan: +3 machines for group 'web' on region 'yyz' with size 'shared-cpu-1x' +3 machines for group 'web' on region 'nrt' with size 'shared-cpu-1x' +1 machines for group 'worker' on region 'yyz' with size 'shared-cpu-1x' +1 machines for group 'worker' on region 'nrt' with size 'shared-cpu-1x' ? Scale app my-app-name? Yes Executing scale plan Created 9080eeddc29387 group:web region:yyz size:shared-cpu-1x ... Created 1781779b52d489 group:worker region:nrt size:shared-cpu-1x ``` ```bash cmd theme={null} fly scale show ``` ```out out theme={null} ... Groups NAME COUNT KIND CPUS MEMORY REGIONS web 10 shared 1 256 MB nrt(5),yyz(5) worker 6 shared 1 256 MB nrt(3),yyz(3) ``` If an app has more capacity than required in a particular region, then you can also scale Machines by region. The following example scales both processes down by one Machine in only the yyz (Toronto) region: ```bash theme={null} fly scale count web=4 worker=2 --region yyz ``` As requested, nrt is unchanged, but in yyz the `web` process is scaled down to 4 and `worker` to 2 Machines. ``` Groups NAME COUNT KIND CPUS MEMORY REGIONS web 9 shared 1 256 MB nrt(5),yyz(4) worker 5 shared 1 256 MB nrt(3),yyz(2) ``` You can also specify a single process group to scale using the `--process-group` option: ```bash theme={null} fly scale count 4 --process-group web --region nrt,yyz ``` ## Add a new region Adding a region to an app just means putting at least one Machine there. For example, add syd (Sydney) to an app's regions by scaling up from 0 there: ```bash theme={null} fly scale count 2 --region syd ``` Now syd will show up in the app's `regions list`: ```bash cmd theme={null} fly regions list ``` ```out out theme={null} Regions [app]: yyz, syd ``` If the app has multiple process groups, specify which process or processes to put in the new region: ``` fly scale count web=1 worker=1 --region syd ``` Alternatively, you can [clone](#scale-up-with-fly-machine-clone) specific Machines to a new region, and the new Machine inherits the process group of its source Machine. ## Balance Machines between regions with `--max-per-region` Use `--max-per-region` for added control over Machine placement by region if the changes proposed by `fly scale count` look unbalanced. This option caps the resulting count of Machines in any one region to the number you provide. You can also use `--max-per-region` to redistribute the Machine count more evenly among regions. For example, on an app with 8 machines in yyz (Toronto), 2 in yul (Montreal) and only 1 in ewr (Secaucus): ``` NAME COUNT KIND CPUS MEMORY REGIONS app 11 shared 1 256 MB ewr,yul(2),yyz(8) ``` ```bash theme={null} fly scale count 11 --max-per-region 5 ``` ``` NAME COUNT KIND CPUS MEMORY REGIONS app 11 shared 1 256 MB ewr(3),yul(3),yyz(5) ``` You can combine `--max-per-region` with process groups and `--region` in a single `fly scale count` command. ## Scale to zero and back up Scale to zero Machines with `fly scale count`. Don't leave out any processes: ```bash theme={null} fly scale count web=0 worker=0 ``` Check that it worked: ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: my-app-name Groups NAME COUNT KIND CPUS MEMORY REGIONS ``` Now if there are no Machines in any process groups, we can't just scale up, because `fly scale count` relies on essentially cloning an existing Machine. But we can simply redeploy: ```bash theme={null} fly deploy ``` ```bash cmd theme={null} fly scale show ``` ```out out theme={null} ... Groups NAME COUNT KIND CPUS MEMORY REGIONS web 2 shared 1 256 MB yyz(2) worker 2 shared 1 256 MB yyz(2) ``` `fly deploy` creates two Machines per process, for resilience purposes. We're back in business and can scale as desired! ## Scale up with `fly machine clone` You can add Machines to an app by cloning Machines. The new Machine will be, as you would expect, a copy of the specified Machine, and will belong to the same process group. If the original Machine has a [Fly Volume](/volumes) attached, an empty volume will be provisioned for the new Machine. It's up to you to decide what to put on the new volume; `fly machine clone` will not automatically copy the contents of the original Machine's volume. The following commands create three new Machines by cloning an existing Machine: ``` $ fly machine clone 21781973f03e89 $ fly machine clone --region syd 21781973f03e89 $ fly machine clone --region nrt 21781973f03e89 ``` ## Scale down with `fly machine destroy` Use `fly machine stop` and `fly machine destroy` to scale down the app by removing specific Machines: ``` $ fly machine stop 9080524f610e87 $ fly machine destroy 9080524f610e87 ``` If a Machine is misbehaving (for instance, it's not `stop`ping successfully), you can use `fly machine destroy --force` to get rid of it. ```bash theme={null} fly machine destroy --force 0e286039f42e86 ``` > [!NOTE] > > 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](/launch/scale-machine) How to use vertical scaling. * [Autoscale based on metrics](/launch/autoscale-by-metric) Learn about automatic scaling using metrics. * [flyctl scale — CLI reference](/flyctl/cmd/fly_scale) Read a full breakdown of the scale subcommands. * [Machine Placement and Regional Capacity](/machines/guides-examples/machine-placement) Find out how region choice and resource availability affect scaling success. # Scale Machine CPU and RAM Source: https://docs.fly.io/launch/scale-machine Scale Machine CPU and RAM on Fly.io with the [[vm]] section in fly.toml, fly scale vm presets, fly scale memory, per process group, or fly machine update. You can scale Machine memory and CPU settings for entire process groups in apps that are managed by Fly Launch (`fly deploy` + `fly.toml`). If you haven't defined any process groups, then commands and settings are applied to all the Machines in your app (in the default `app` process group). > [!NOTE] > > You can scale an app even if it has crashed. Its Machines are restarted with the new specification, however, if you redeploy the app, then any VM settings in `fly.toml` take precedence. ## Machine size configuration precedence 1. **The `[[vm]]` section in `fly.toml`**: The `fly deploy` and `fly scale count` commands respect the VM size configurations in your app's `fly.toml` file. 2. **Existing Machine sizes in the app:** If no VM size is set in `fly.toml`, then `fly deploy` won't change existing Machines, and `fly scale` will use existing Machines to infer new Machine sizes. 3. **Default Machine size of `shared-cpu-1x`:** If no VM size is set in `fly.toml`, and there are no existing Machines to infer size from, then the default Machine size is used. ## Check the VM resources on an app Here's a simple web app with three Machines running in different regions: two in Toronto and one in Tokyo. All the app's Machines belong to the default process group, `app`, since no other [processes](/launch/processes) exist. ```bash cmd theme={null} fly status ``` ```out out theme={null} App Name = testrun Owner = personal Hostname = testrun.fly.dev Image = testrun:deployment-01GWZY7ZVJ2HNED4B0KZBPS3AQ Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 17811943f031d8 3 yyz stopped 1 total, 1 passing 2024-02-07T15:34:57Z app 328749d3c53958 3 yyz stopped 1 total, 1 passing 2024-01-23T19:39:50Z app 908057ef21e487 3 nrt started 1 total, 1 passing 2024-01-23T19:39:51Z ``` `fly scale show` shows the CPU and RAM settings for all the Machines deployed using `fly deploy` under this app. ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: testrun Groups NAME COUNT KIND CPUS MEMORY REGIONS app 3 shared 1 256 MB nrt,yyz(2) ``` These Machines are running at the `shared-cpu-1x` preset scale, with a single shared vCPU and 256MB RAM. ## Add Machine size configuration to `fly.toml` With the `[[vm]]` section in `fly.toml`, you can set default Machine VM memory and CPU configurations, which [take precedence](#machine-size-configuration-precedence) when you run commands like `fly deploy` or `fly scale count`. This example shows a very simple config that specifies the `shared-cpu-2x` preset with 2GB of RAM: ```toml theme={null} [[vm]] size = "shared-cpu-2x" memory = "2gb" ``` If you don't include a process group in the `[[vm]]` section, then the settings apply to all process groups in your app. Add another `[[vm]]` section if you want different CPU or memory settings for specific process groups. For details and more settings, see [The `vm` section](/reference/configuration#the-vm-section) in the `fly.toml` reference. ## Scale VM memory and CPU with flyctl Use [`fly scale`](/flyctl/cmd/fly_scale) subcommands to apply VM memory and CPU settings to all Machines: `fly scale vm` applies a preset CPU/RAM combination; `fly scale memory` sets RAM separately, for cases when the preset's RAM is not enough. > [!NOTE] > > **Important:** If you make changes using `fly scale vm` or `fly scale memory`, the VM settings in `fly.toml` take precedence when you redeploy the app. ### Select a preset CPU/RAM combination There are a number of VM size presets available. See the list of valid named presets with `fly platform vm-sizes`. Scale to a different preset using `fly scale vm`. In general, you should choose a named VM "size" based on your desired CPU type and scale; RAM can be increased separately. ```bash cmd theme={null} fly scale vm shared-cpu-2x ``` ```out out theme={null} Updating machine 148ed599c14189 Waiting for 148ed599c14189 to become healthy (started, 1/1) Machine 148ed599c14189 updated successfully! Updating machine 32874400f35285 Waiting for 32874400f35285 to become healthy (started, 1/1) Machine 32874400f35285 updated successfully! Updating machine 9080e6e1f94987 Waiting for 9080e6e1f94987 to become healthy (started, 1/1) Machine 9080e6e1f94987 updated successfully! Scaled VM Type to 'shared-cpu-2x' CPU Cores: 2 Memory: 512 MB ``` Check that the `app` process group has had this scale applied: ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: testrun Groups NAME COUNT KIND CPUS MEMORY REGIONS app 3 shared 2 512 MB nrt,yyz(2) ``` You can also confirm that an individual Machine's config matches this, using `fly machine status `: ```bash cmd theme={null} fly machine status 148ed599c14189 ``` ```out out theme={null} Machine ID: 148ed599c14189 Instance ID: 01GX6Q2WE04M85XTHGPYGJK4X6 State: started VM ... Process Group = app CPU Kind = shared vCPUs = 2 Memory = 512 ... ``` Looks good! ### Add RAM If you are happy with the provisioned CPU resources, but want more memory, then use `fly scale memory` to top up the RAM. If your app crashes with an out-of-memory error, then scale up its RAM. Flyctl restarts the Machines to use the new setting. Scaling memory this way lets you test your app with more or less RAM, before optionally [setting memory more permanently in `fly.toml`](#add-machine-size-configuration-to-fly-toml). ```bash cmd theme={null} fly scale memory 4096 ``` ```out out theme={null} Updating machine 32874400f35285 Waiting for 32874400f35285 to become healthy (started, 1/1) Machine 32874400f35285 updated successfully! Updating machine 148ed599c14189 Waiting for 148ed599c14189 to become healthy (started, 1/1) Machine 148ed599c14189 updated successfully! Updating machine 9080e6e1f94987 Waiting for 9080e6e1f94987 to become healthy (started, 1/1) Machine 9080e6e1f94987 updated successfully! Scaled VM Type to 'shared-cpu-2x' CPU Cores: 2 Memory: 4096 MB ``` ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: testrun Groups NAME COUNT KIND CPUS MEMORY REGIONS app 3 shared 2 4096 MB nrt,yyz(2) ``` If you try to set an incompatible CPU/RAM combination through `fly scale memory`, flyctl will let you know. There's a list of allowed CPU/RAM combinations and their prices on [our Pricing page](/about/pricing). ### Scale by process group Use the `--process-group` option to specify the process group to scale, with either `fly scale vm` or `fly scale memory`. > [!NOTE] > > **Note**: The `--process-group` option is aliased to `-g` for faster command entry. Here's an app with two process groups defined: ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: mr18-2 Groups NAME COUNT KIND CPUS MEMORY REGIONS worker 2 shared 1 512 MB ams,yyz web 1 shared 1 512 MB yyz ``` Say the workers are constantly crunching data and need to be bigger. You can scale a single process group using the `--process-group` option: ```bash cmd theme={null} fly scale vm performance-2x --process-group worker ``` ```out out theme={null} Updating machine 0e286561f35586 No health checks found Machine 0e286561f35586 updated successfully! Updating machine 32873d9b012048 No health checks found Machine 32873d9b012048 updated successfully! Scaled VM Type for 'worker' to 'performance-2x' CPU Cores: 2 Memory: 4096 MB ``` Check the result: ```bash cmd theme={null} fly scale show ``` ```out out theme={null} VM Resources for app: mr18-2 Groups NAME COUNT KIND CPUS MEMORY REGIONS worker 2 performance 2 4096 MB ams,yyz web 1 shared 1 512 MB yyz ``` ## Machines not belonging to Fly Launch If an app has Machines that don't belong to Fly Launch (in other words, if you created Machines using `fly machine run` or the Machines API), then `fly status` will warn you of their existence: ```text theme={null} Found machines that aren't part of Fly Launch, run fly machines list to see them. ``` The app-wide `fly scale` commands and any VM settings in `fly.toml` don't apply to these Machines, but you can scale any Machine individually with `fly machine update`: ``` fly machine update --vm-size shared-cpu-2x 21781973f03e89 fly machine update --vm-memory 1024 21781973f03e89 fly machine update --vm-cpus 2 21781973f03e89 ``` If you try to set an incompatible CPU/RAM combination through `fly machine update --vm-memory` or `fly machine update --vm-cpus`, flyctl will let you know. Learn more about [individual Machine sizing with flyctl and the Machines API](/machines/guides-examples/machine-sizing). # Add volume storage to a Fly Launch app Source: https://docs.fly.io/launch/volume-storage Attach Fly Volumes to a Fly Launch app: add [mounts] to fly.toml, create one volume per Machine and region, deploy, and verify with fly volumes list. Fly Volumes are local persistent storage for [Fly Machines](/machines). Learn more: * [How Fly Volumes work](/volumes/overview) * [How to manage volumes with flyctl commands](/volumes/volume-manage) * [How to manage volume snapshots (backups)](/volumes/snapshots) ## Launch a new app with a Fly Volume Use [Fly Launch](/launch/index) to create a new app with one Machine and an attached volume, and then clone the Machine to scale out. 1. Launch a new app from your project source directory with the `--no-deploy` option so it doesn't deploy automatically. Include any required options for deploying your app, like `--image` or `--dockerfile`. ```bash theme={null} fly launch --no-deploy ``` `fly launch` creates a `fly.toml` app configuration file in your project source directory. 2. After the app is created, add a [`[mounts]` section](/reference/configuration#the-mounts-section) in the `fly.toml`, where `source` is the volume name and `destination` is the directory where the volume should be mounted on the Machine file system. For example: ```toml theme={null} [mounts] source = "myapp_data" destination = "/data" ``` > [!NOTE] > > **Note:** You can't mount a volume with `destination="/"` since `/` is used for the root file system. 3. Deploy the app: ```bash theme={null} fly deploy ``` 4. [Confirm that the volume is attached to a Machine](#confirm-the-volume-is-attached-to-a-machine). 5. (Recommended only if your app handles replication) Clone the first Machine to scale out to two Machines with volumes: ```bash theme={null} fly machine clone ``` List volumes to check the result: ```bash theme={null} fly volumes list ``` Example output showing two volumes with attached Machines: ```out theme={null} ID STATE NAME SIZE REGION ZONE ENCRYPTED ATTACHED VM CREATED AT vol_ez1nvxkwl3jrmxl7 created data 1GB lhr 4de2 true 91851edb6ee983 39 seconds ago vol_zmjnv8m81p5rywgx created data 1GB lhr b6a7 true 5683606c41098e 7 minutes ago ``` > [!WARNING] > > **Warning:** `fly machine clone` doesn't write data into the new volume. ## Add volumes to an existing app Add a volume to an app created with [Fly Launch](/launch/index). 1. Add a [`[mounts]` section](/reference/configuration#the-mounts-section) in the app's `fly.toml`, where `source` is the volume name and `destination` is the directory where the volume should be mounted on the Machine file system. For example: ```toml theme={null} [mounts] source = "myapp_data" destination = "/data" ``` 2. Run `fly status` to check the [regions](/reference/regions) of the Machines and then create the volume in the same regions as your app's Machines. For example: ```bash theme={null} fly volumes create -r ``` 3. Repeat step 2 for each Machine in the process group. If you create an app using the `fly launch` command, then the app will usually have two Machines in the `app` process by default. > [!NOTE] > > **Note:** If you have multiple Machines, then you need to create an equal number of volumes with the same name. For example, your app's `[mounts]` config specifies `source="myapp_data"` and you have three Machines in `bos` (Boston), then you need to create three volumes named `myapp_data` in `bos`. Every volume has a unique ID to allow for multiple volumes with the same name. 4. Deploy the app: ```bash theme={null} fly deploy ``` 5. [Confirm that the volume is attached to a Machine](#confirm-the-volume-is-attached-to-a-machine). ## Confirm the volume is attached to a Machine Use flyctl to check the status of volumes and Machines. ### List the Machines List Machines to check attached volumes: ```bash theme={null} fly machine list ``` Example output: ```out theme={null} 1 machines have been retrieved from app my-app-name. View them in the UI here my-app-name ID NAME STATE REGION IMAGE IP ADDRESS VOLUME CREATED LAST UPDATED APP PLATFORM PROCESS GROUP SIZE 328773d3c47d85 my-app-name stopped yul flyio/myimageex:latest fdaa:2:45b:a7b:19c:bbd4:95bb:2 vol_6vjywx86ym8mq3xv 2023-08-20T23:09:24Z 2023-08-20T23:16:15Z v2 app shared-cpu-1x:256MB ``` ### List the volumes List volumes to check attached Machines: ```bash theme={null} fly volumes list ``` Example output: ```out theme={null} ID STATE NAME SIZE REGION ZONE ENCRYPTED ATTACHED VM CREATED AT vol_zmjnv8m81p5rywgx created data 1GB lhr b6a7 true 5683606c41098e 3 minutes ago ``` ### SSH into the Machine View the volume in the Machine file system: ```bash theme={null} fly ssh console -s -C df ``` Example output showing a 1GB volume mounted at `/data`: ```out theme={null} ? Select VM: lhr: 5683606c41098e fdaa:0:3b99:a7b:7e:3155:9844:2 nameless-feather-6339 Connecting to fdaa:0:3b99:a7b:7e:3155:9844:2... complete Filesystem 1K-blocks Used Available Use% Mounted on devtmpfs 103068 0 103068 0% /dev /dev/vda 8191416 172748 7582856 3% / shm 113224 0 113224 0% /dev/shm tmpfs 113224 0 113224 0% /sys/fs/cgroup /dev/vdb 1011672 2564 940500 1% /data ``` The volume is mounted in the directory specified by the `destination` field in the `[mounts]` section of the `fly.toml` file, or the `attach-volume` option for cloned Machines. ## Access a volume Access and write to a volume on a Machine just like a regular directory. For Machines managed with Fly Launch, the `destination` under `[mounts]` in `fly.toml` is the path for the mounted volumes. For unmanaged Machines, you specify the mount path when you [add a volume to a cloned Machine](/volumes/volume-manage#add-a-volume-to-an-unmanaged-machine) or [create a Machine and attach a volume](/machines/flyctl/fly-machine-run#mount-a-fly-volume). ## Related topics * [Fly Volumes overview](/volumes/overview) * [`mounts` section](/reference/configuration#the-mounts-section) in the `fly.toml` app configuration file * [Create and manage volumes](/volumes/volume-manage) * [Manage volume snapshots](/volumes/snapshots) * [Scale an app with volumes](/launch/scale-count#scale-an-app-with-volumes) # Fly Managed Postgres Source: https://docs.fly.io/links/mpg # Backing up your LiteFS cluster Source: https://docs.fly.io/litefs/backup Back up a LiteFS cluster with litefs export, gzip, and rolling S3 copies, or stream continuously to Tigris with Litestream for point-in-time restore. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. While LiteFS replicates data between nodes and supports failover, it does not currently have a disaster recovery system in place in the event you lose your entire cluster. These are some strategies you can use to reduce your risk of data loss in the event of a bug or disk corruption. ## Periodic backup via export The simplest approach is to export your database via the [litefs export](/litefs/export) command and then upload that file to long-term storage such as S3. The `export` command is safe to use on a live database and can even be run remotely by specifying the `-url` flag. Once the database is downloaded, it can be compressed & uploaded to S3. ```sh theme={null} # Download current snapshot the "my.db" database to a local file. litefs export -name my.db /path/to/backup # Compress the file. gzip /path/to/backup # Upload the file to S3. aws s3 cp /path/to/backup.gz s3://mybucket/backup.gz ``` It's also recommended to perform a rolling backup based on either hour, day or month depending on cost requirements. ```sh theme={null} # 1-day, rolling hourly backup aws s3 cp /path/to/backup.gz s3://mybucket/backup-`date +%H`.gz # 1-month, rolling daily backup aws s3 cp /path/to/backup.gz s3://mybucket/backup-`date +%d`.gz # 1-month, rolling hourly backup aws s3 cp /path/to/backup.gz s3://mybucket/backup-`date +%d%H`.gz ``` This backup can be run on any of your nodes as even the replicas should have minimal lag behind the primary. If you run this on multiple nodes (such as all your candidates), make sure they are backing up to different storage locations so they do not overwrite one another. ## Continuous backup via Litestream [Litestream](https://litestream.io/) is a tool for continuously streaming incremental backups to S3-compatible object storage. You can use it on top of LiteFS to provide point-in-time recovery in case your cluster fails for any reason. > [!WARNING] > > This approach only works if you are running a static lease on your cluster. ### Initialize your bucket First, you'll need to create a bucket on S3-compatible storage. In this example, we'll use [Tigris](https://www.tigrisdata.com/) since it integrates nicely with Fly.io. ```sh theme={null} fly storage create ``` This command will return several configuration variables. Set the AWS access key ID and secret access key as secrets in your application: ```sh theme={null} fly secrets set AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... ``` ### Set up your configuration file Once your bucket is created, you'll need to create a [Litestream configuration file](https://litestream.io/reference/config/). In this example, change `my.db` and `mybucket` to your respective database file and bucket names: ```yml theme={null} dbs: - path: /litefs/my.db meta-path: /var/lib/litefs/my.db-litestream replicas: - type: s3 bucket: "mybucket" path: "my.db" endpoint: "fly.storage.tigris.dev" force-path-style: true ``` If you save this file to `/etc/litestream.yml` then Litestream will pick it up automatically for any Litestream commands you use. The `meta-path` should be a path on a persistent volume. In this example, we're assuming that `/var/lib/litefs` has been mounted from a volume. ### Running Litestream Litestream should be run after LiteFS has been initialized so we can specify it in the LiteFS `exec`. Litestream can run its own `exec` so you should specify your application there. ```yml theme={null} # litefs.yml exec: "litestream replicate -exec myapp" ``` Litestream should only be run on the primary node. It requires write access to the database so it will not run on read-only replicas. ### Verifying your setup Once you have your application running, you should be able to run Litestream's [restore](https://litestream.io/reference/restore/) command to fetch the current version of the database: ```sh theme={null} litestream restore -o ~/myrestored.db /litefs/my.db ``` If you need to recover from a loss of the database, you'll need to restore your SQLite database to a file and then use [litefs import](/litefs/import) to import it into your cluster. Litestream cannot restore directly into your LiteFS mount. # Using LiteFS Cloud for Backups Source: https://docs.fly.io/litefs/cloud-backups LiteFS Cloud was retired in October 2024, so this is kept for reference. Covers creating a cluster, upgrading to v0.5.0, and setting LITEFS_CLOUD_TOKEN. > [!WARNING] > > **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). > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. The simplest way to implement disaster recovery for your LiteFS cluster is to use Fly.io's LiteFS Cloud for backups. LiteFS Cloud provides reliable backup and restore functionality, which reduces the risk of data loss in the event that your cluster is lost. You'll also be able to restore your cluster to any point in time (not just the most recent snapshot), in case you need to revert your state. In this doc, we'll show you how to add LiteFS Cloud to an existing LiteFS cluster. If you're not using LiteFS yet, take a look at the [getting started guide](/litefs/getting-started-fly), which walks you through setting LiteFS up for your app, including LiteFS Cloud. If you'd rather implement your own backups, take a look at the [Backing up your LiteFS cluster](/litefs/backup) docs. ## Creating a LiteFS Cloud cluster If you want to back up your database using LiteFS Cloud, you'll need to create a LiteFS Cloud cluster. You can do this in the Fly.io dashboard. First, [sign up for an account](/getting-started/sign-up-sign-in) if you haven't already. After you're signed in, navigate to the LiteFS Cloud section on the left navbar, and then use the Create button to create a new cluster. [Here's a handy link to the LiteFS Cloud dashboard](https://fly.io/dashboard/personal/litefs) if you prefer! When you create your LiteFS Cloud cluster, you'll be asked to save an auth token. Keep this handy - you'll use it below. ## Configuring LiteFS to use LiteFS Cloud ### Update LiteFS Before configuring LiteFS Cloud, you must update your LiteFS to a version that supports LiteFS Cloud. Update to `v0.5.0` or greater. You can do this by [installing the latest version](/litefs/getting-started-fly#installing-litefs). ### LiteFS Cloud Configuration When you created your LiteFS Cloud cluster, you got a LiteFS Cloud API authentication token. (If you lost it, you can create a new one in [the LiteFS Cloud dashboard](https://fly.io/dashboard/personal/litefs).) This token should be made available to LiteFS via the `LITEFS_CLOUD_TOKEN` environment variable. If you're running on Fly.io, you can add a secret for it with: ```bash theme={null} fly secrets set LITEFS_CLOUD_TOKEN="litefs cloud auth token from dashboard" ``` Previous non-cloud litefs databases will automatically be synched to the litefs cloud cluster. If you're not running on Fly.io, you need to set the `LITEFS_CLOUD_TOKEN` environment variable, and then restart LiteFS on each node. How you do this depends on how you have configured LiteFS. If you're running LiteFS separately from your app, then for each node, you should: * Update your configuration with the new environment variable * Stop your app (so that it's not reading/writing data anymore) * Restart litefs * Start your app again! If you're running LiteFS via `systemd`, you can restart it with: ```bash theme={null} systemctl restart litefs ``` Remember that this needs to be run for every LiteFS node. # Restoring from a LiteFS Cloud Backup Source: https://docs.fly.io/litefs/cloud-restore Archived: LiteFS Cloud was retired in 2024. Restoring a database to any point in the past 30 days at 5-minute granularity, and why a write was needed. > [!WARNING] > > **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). > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview You can restore your LiteFS database to any point in time in the last 30 days, with 5 minute granularity. This means that when you select a time, and LiteFS Cloud will find the most recent snapshot from before that timestamp, which is guaranteed to have been the state of the database no more than 5 minutes before the timestamp you selected. Note: if you decide that you made a mistake restoring from snapshot, you can also get your pre-restore state back, simply by restoring again, and selecting the time right before you did the first (erroneous) restore! ## Restoring your LiteFS database To restore your LiteFS database to a snapshot, navigate to the [Fly.io LiteFS dashboard](https://fly.io/dashboard/personal/litefs), find the LiteFS Cloud cluster and database you would like to restore, and click the "view backups" link. Then you can select a date and time to restore from. Since restore is a destructive function (you are likely to lose up to 5 minutes of data if you need to "revert" the restore), you'll need to type the name of the cluster and database to make sure you're restoring the database you intend. The restore normally takes just a few seconds, or up to a couple of minutes if your database is very large. However, you won't see the new data until after you make a write to your database. For production applications (where writes are frequent), this is unlikely to be noticeable, but if you're testing it out, you may need to manually make a write to see changes! ## Limitations ### Snapshot age LiteFS Cloud stores "snapshots" as a series of [LTX files](https://github.com/superfly/litefs/blob/main/docs/ARCHITECTURE.md#lite-transaction-files-ltx). For cost and performance reasons, LiteFS Cloud compacts these files into files containing transactions from five minute intervals. This means that when you select a timestamp for restoring, LiteFS Cloud takes the most recent file available from before that timestamp, which means that you may miss some transactions that happened within the five minute period before the timestamp you selected. ### Snapshot restore requires database write When you configure LiteFS Cloud, your LiteFS database considers LiteFS Cloud to be the authoritative source for the expected state of the database. When you restore a snapshot, LiteFS Cloud downloads the appropriate files, compacts them, and sends them to LiteFS the next time it asks for the state. However, currently LiteFS only asks LiteFS Cloud for the expected state on write, which means that you need to make a write before LiteFS updates the database state when you restore a snapshot. # LiteFS Config Reference Source: https://docs.fly.io/litefs/config Reference for each litefs.yml section: fuse and data dirs, exec, HTTP API port 20202, proxy, Consul or static leases, env var expansion, and trace logging. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. Most of the configuration options for LiteFS are set via a `litefs.yml` config file. You can find a fully-commented version of [`litefs.yml`](https://github.com/superfly/litefs/blob/main/cmd/litefs/etc/litefs.yml) in the LiteFS repository. ## Config file search path LiteFS will search the following paths to find the config file: 1. The current directory: `./litefs.yml` 2. The home directory: `~/litefs.yml` 3. The system configuration directory: `/etc/litefs.yml` You can also explicitly set the config path using a command line flag: ```sh theme={null} litefs mount -config /path/to/litefs.yml litefs run -config /path/to/litefs.yml ``` ## Environment Variables LiteFS can evaluate environment variables within the configuration file. Variables can be referenced using either the `$MY_VAR` syntax or the `${MY_VAR}` syntax. Basic string equality & inequality expressions can also be evaluated using either single or double quotes: ``` ${MY_VAR == "xyz"} ``` ``` ${MY_VAR != "xyz"} ``` Environment variable evaluation can be disabled by specifying the `-no-expand-env` flag from the command line arguments. ## FUSE directory LiteFS uses a [FUSE](https://www.kernel.org/doc/html/latest/filesystems/fuse.html) directory mount to intercept Linux file system calls to track when SQLite transactions start and end so they can be replicated. The recommended path is `/litefs` and it is the directory your application should use when interacting with the database. ```yml theme={null} # The FUSE section handles settings on the FUSE file system. FUSE # provides a layer for intercepting SQLite transactions on the # primary node so they can be shipped to replica nodes # transparently. fuse: # Required. This is the mount directory that applications will # use to access their SQLite databases. dir: "/litefs" # Enable mounting of the file system by non-root users. # You must enable the 'user_allow_other' option in /etc/fuse.conf as well. allow-other: false # The debug flag enables debug logging of all FUSE API calls. # This will produce a lot of logging. Not for general use. debug: false ``` ## Internal data directory LiteFS acts as a passthrough file system so all your database files still need to be stored internally on a regular file system. This internal data directory should be saved on a persistent volume so you don't lose your data between restarts. LiteFS also stores transaction files (called *LTX files*) in this directory. These files are periodically removed through an internal process called *retention enforcement*. ```yml theme={null} # The data section specifies where internal LiteFS data is stored # and how long to retain the transaction files. # # Transaction files are used to ship changes to replica nodes so # they should persist long enough for replicas to retrieve them, # even in the face of a short network interruption or a redeploy. # Under high load, these files can grow large so it's not advised # to extend retention too long. data: # Path to internal data storage. dir: "/var/lib/litefs" # If true, compresses LTX files using LZ4 compression. Enabled by default. compress: true # Duration to keep LTX files. Latest LTX file is always kept. retention: "10m" # Frequency with which to check for LTX files to delete. retention-monitor-interval: "1m" ``` ## Supervisor / Exec LiteFS provides the option to run as a supervisor to another process. Typically, this would be the command you run for your application. This is useful so that LiteFS can mount itself and connect to the cluster before starting your application. It will pass signals to your application and it will automatically shutdown when your application shuts down. ```yml theme={null} # The exec field specifies a command to run as a subprocess of # LiteFS. This command will be executed after LiteFS either # becomes primary or is connected to the primary node. LiteFS # will forward signals to the subprocess and LiteFS will # automatically shut itself down when the subprocess stops. # # This can also be specified after a double-dash (--) on the # command line invocation of the 'litefs mount' command. exec: "myapp -addr :8080" ``` ## HTTP API Server LiteFS communicates with other nodes' API over HTTP. This can be configured in the `http` section of the config. The default port for LiteFS is `20202`. **This port MUST NOT be publicly accessible.** ```yml theme={null} # This section defines settings for the LiteFS HTTP API server. # This API server is how nodes communicate with each other. http: # Specifies the bind address of the HTTP API server. addr: ":20202" ``` ## HTTP Proxy Server LiteFS includes a built-in HTTP proxy server that handles data consistency and request forwarding for most web applications. It is disabled by default but can be enabled by specifying a bind address & target. ```yml theme={null} # This section defines settings for the option HTTP proxy. # This proxy can handle primary forwarding & replica consistency # for applications that use a single SQLite database. proxy: # Specifies the bind address of the proxy server. addr: ":8080" # The hostport of the target application. If blank, proxy is disabled. target: "localhost:8081" # The name of the database used for TXID tracking. db: "my.db" # If true, enables verbose logging of requests by the proxy. debug: false # List of paths that are ignored by the proxy. The asterisk is # the only available wildcard. These requests are passed # through to the target as-is. passthrough: ["/debug/*", "*.png"] # The duration to hold write requests when no primary is available. # This is helpful when a primary switches over and there is a small window # where the new primary is unknown to the replica. primary-redirect-timeout: "5s" ``` ## Lease Management Leadership is managed by using an external lease system. This can be done using either Consul or by specifying a single, static primary node. ```yml theme={null} # The lease section defines how LiteFS creates a cluster and # implements leader election. For dynamic clusters, use the # "consul". This allows the primary to change automatically when # the current primary goes down. For a simpler setup, use # "static" which assigns a single node to be the primary and does # not failover. lease: # Required. Must be either "consul" or "static". type: "consul" # Required. The URL for this node's LiteFS API. # Should match HTTP port. advertise-url: "http://$HOSTNAME:20202" # Sets the hostname that other nodes will use to reference this # node. Automatically assigned based on hostname(1) if not set. hostname: "" # Specifies whether the node can become the primary. If using # "static" leasing, this should be set to true on the primary # and false on the replicas. candidate: true # Filtered list of databases to replicate from the primary to this node. # If no databases are specified, all databases are replicated. You may only # filter databases on non-candidate nodes. databases: [ "my.db", "myother.db" ] ``` ### Consul Leasing Consul is the recommended lease backend as it allows leadership to change when the primary node stops. The `url` and `advertise-url` are required so that the node can connect to Consul and broadcast its LiteFS API URL. ```yml theme={null} lease: # A Consul server provides leader election and ensures that the # responsibility of the primary node can be moved in the event # of a deployment or a failure. consul: # Required. The base URL of the Consul server. url: "http://myhost:8500" # Required. The key used for obtaining a lease by the primary. # This must be unique for each cluster of LiteFS servers key: "" # Length of time before a lease expires. The primary will # automatically renew the lease while it is alive, however, # if it fails to renew in time then a new primary may be # elected after the TTL. This only occurs for unexpected loss # of the leader as normal operation will allow the leader to # handoff the lease to another replica without downtime. # # Consul does not allow a TTL of less than 10 seconds. ttl: "10s" # Length of time after the lease expires before a candidate # can become leader. This buffer is intended to prevent # overlap in leadership due to clock skew or in-flight calls. lock-delay: "1s" ``` ### Static Leasing If you do not wish to run a separate Consul instance and you can tolerate downtime of your primary node, you can run LiteFS with a static lease. A static lease means that only a single, fixed node will ever be the primary. There is no additional configuration section for static leasing. Simply set the `lease.candidate` config field to `true` on the primary and `false` on the replicas. You will need to set `lease.hostname` and `lease.advertise-url` on the primary as well. ## Other options ### Exit on error ```yml theme={null} # If true, then LiteFS will not exit if there is a validation # issue on startup. This can be useful for debugging issues as # it avoids constantly restarting the node on ephemeral hosting. exit-on-error: false ``` ### Skip sync ```yml theme={null} # If true, then LiteFS will not wait until the node becomes the # primary or connects to the primary before starting the subprocess. skip-sync: false ``` ### Trace Log The trace log provides a very low-level log of events inside LiteFS. It is typically only used for debugging issues and is turned off by default. This log can produce a lot of disk IO when enabled. ```yml theme={null} # The tracing section enables a rolling, on-disk tracing log. # This records every operation to the database so it can be # verbose and it can degrade performance. This is for debugging # only and should not typically be enabled in production. tracing: # Toggles tracing if 'path' is also set. Defaults to true. enabled: true # Output path on disk. path: "/var/log/lifefs/trace.log" # Maximum size of a single trace log before rolling. # Specified in megabytes. max-size: 64 # Maximum number of trace logs to retain. max-count: 10 # If true, historical logs will be compressed using gzip. compress: true ``` # Disaster Recovery from LiteFS Cloud Source: https://docs.fly.io/litefs/disaster-recovery Deprecated LiteFS Cloud guide to rebuilding a lost cluster: run only the primary, fix the clusterid file, clear the Consul key conflict, re-clone replicas. > [!WARNING] > > **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). > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. The biggest advantage of using LiteFS Cloud is that you have backups to restore from in case of an unfortunate situation where you lose all the nodes in your cluster. In this doc, we'll show you how to restore your cluster from LiteFS Cloud if you've lost all the nodes. > [!NOTE] > > **Important:** You should only follow the steps in this document if you've lost all > nodes of your LiteFS cluster. Otherwise, you can > [restore from a LiteFS Cloud backup](/litefs/cloud-restore). ## Start your app up with only the primary node It's simpler to recover a single node, rather than several nodes, so to start out with, it's best to remove all replica nodes and run just a primary node. If your app hasn't already restarted before you got here, just restart it with only the primary node and move on to [the next step](#find-the-correct-cluster-id)! If your app has already restarted with multiple nodes running, you should temporarily remove all of the replicas. If you're running your app on the Fly Platform, you can see the machines running your app with: ```sh theme={null} fly status ``` You'll see something like this: ``` Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 784e900c2de378 1 ams started primary 2023-07-10T13:05:40Z app e784e471b39208 1 ord started replica 2023-07-10T13:01:16Z ``` Then destroy each machine that's marked `replica`: ```sh theme={null} fly machines destroy e784e471b39208 ``` You can re-run `fly status` after you're done, to confirm you only have the primary running. ## Find the correct cluster id When your app restarted, LiteFS generated a new cluster id, which is different than the cluster id stored in LiteFS Cloud. This is a safety measure: we don't want to accidentally overwrite data in a cluster if you inadvertently set the wrong LiteFS Cloud token for the app. You'll see some error messages in the LiteFS logs, similar to this: ``` level=INFO msg="backup stream failed, retrying: fetch position map: backup client error (422): cluster id mismatch, expecting \"LFSC25AD000F32ED02A9\"" ``` If you're running on the Fly Platform, you can see the logs with: ```sh theme={null} fly logs ``` Note the cluster id specified at the end of the log message: `LFSC25AD000F32ED02A9` in our example. Copy this value to use later. ## Update the cluster id Now that you have the correct cluster id, you can connect to your primary node and update the cluster id file. If your app is deployed on the Fly Platform, you can connect with: ```sh theme={null} fly ssh console ``` Then update the `clusterid` file: ```sh theme={null} echo LFSC25AD000F32ED02A9 > /var/lib/litefs/clusterid ``` After this, you should restart LiteFS. If you're using the Fly Platform, close your ssh session, and then restart your app: ```sh theme={null} fly app restart ``` Check your logs again, and confirm you don't see the cluster id error message anymore! You should also take a look at your app and confirm you see the data you expect before you continue. ## Resolve consul key error If you're running on the Fly Platform (or you're using Consul for lease management elsewhere), you may see the following errors in your logs: ``` 2023-07-10T13:30:25Z app[784e900c2de378] ams [info]level=INFO msg="cannot connect, \"consul\" lease already initialized with different ID: LFSCA5E3680C1C2FCFFE" ``` You have two options to resolve this: * change the Consul key value that LiteFS uses. This is easy, but leaves extra Consul keys set. or: * remove the "wrong" key from Consul. This is more difficult but cleaner. ### Easy option: change the Consul key You can simply update the `lease.consul.key` value in the `litefs.yml` file. The old one will still exist in Consul, but LiteFS won't care. For example, you can just add `-v2` to the end of the Consul key value: ```yml theme={null} lease: type: consul ... consul: url: "${FLY_CONSUL_URL}" key: "litefs/${FLY_APP_NAME}-v2" # added '-v2' ``` If you're using the Fly Platform, redeploy your app: ```sh theme={null} fly deploy ``` ### Cleaner option: remove the wrong key from Consul First, you'll need to add `consul` in your image. Connect to your primary node via ssh, and install it there. Here's how to install it: ```sh theme={null} # For debian/ubuntu-based images before bookworm apt-get install consul # From official source: # https://developer.hashicorp.com/consul/install?product_intent=consul#Linux wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list apt update && apt install consul # For alpine-based images apk add consul ``` Get your `FLY_CONSUL_URL`: ```sh theme={null} echo $FLY_CONSUL_URL ``` This url is in the format `https://:{TOKEN}@{HOST}/{PREFIX}`. You'll need each of these values separately. You'll also need the value of `lease.consul.key` from your `litefs.yml` file. Then use the [Consul CLI](https://developer.hashicorp.com/consul/commands/kv) to delete the key with: ```sh theme={null} export TOKEN=(copy the token you found from your consul url) export HOST=(copy the host you found from your consul url) export PREFIX=(copy the prefix you found from your consul url) export LITEFS_CONSUL_KEY=(copy the key from your litefs.yml file) # and then delete the key! consul kv delete -http-addr=https://$HOST -token=$TOKEN $PREFIX/$LITEFS_CONSUL_KEY/clusterid ``` After this is done, restart LiteFS. If your app is deployed on the Fly Platform, you can do this by restarting your app: ```sh theme={null} fly app restart ``` ## Add your replicas back Once your primary node is connected to LiteFS Cloud again and your data has been recovered, you should go ahead and scale back up. If you're using the Fly Platform, that probably looks something like this (replacing `ord` with the region you'd like to run your new replica in): ```sh theme={null} fly machines clone --select --region ord ``` # Reading the LiteFS event stream Source: https://docs.fly.io/litefs/events Stream real-time LiteFS events from GET /events on port 20202 as newline-delimited JSON, including the init, tx, and primaryChange types and their fields. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. LiteFS provides mechanisms for reading the [replication position](/litefs/position) and [current primary status](/litefs/primary) through the FUSE file system mount. However, this involves polling the file system which has overhead and some delay. If you need real-time updates of replication or primary changes, you can also use the LiteFS event stream. The event stream is an HTTP endpoint that sends a newline-delimited JSON stream of events to the client. ## Usage The event stream is accessible at `GET /events` on your LiteFS node: ``` $ curl localhost:20202/events {"type":"init","data":{"isPrimary":true}} {"type":"tx","db":"db","data":{"txID":"0000000000000027","postApplyChecksum":"83b05248774ce767","pageSize":4096,"commit":2,"timestamp":"2023-09-06T19:12:34.985Z"}} {"type":"tx","db":"db","data":{"txID":"0000000000000028","postApplyChecksum":"8467267f644a1b66","pageSize":4096,"commit":2,"timestamp":"2023-09-06T19:12:39.586Z"}} ``` ## Event Types ### `init` The `init` event is always the first event published to an event stream. It includes basic information about whether the local node is the primary and, if not, the hostname of the current primary. More fields may be added to this event in the future. ``` { "type": "init", "data": { "isPrimary": false, "hostname": "myPrimaryHost:20202" } } ``` ### `tx` This event is published every time a new transaction is written to LiteFS. This event will fire whether the transaction is performed locally as the primary or if the transaction was received by a replica. The `txID` field is a hex-formatted transaction ID. The `postApplyChecksum` is a checksum of the entire database after the transaction has been applied. These two fields form the [replication position](/litefs/position). The `commit` field is the size of the database in pages and can be multiplied by the `pageSize` field to give you the total size of the database file in bytes. The `timestamp` field is the timestamp of when the transaction was originally written on the primary node. This is not always reliable as clocks can skew between different servers. ``` { "type": "tx", "db": "db", "data": { "txID": "0000000000000027", "postApplyChecksum": "83b05248774ce767", "pageSize": 4096, "commit": 2, "timestamp": "2023-09-06T19:12:34.985Z" } } ``` ### `primaryChange` This event shows information about the current primary status. It is delivered whenever the status changes such as when the node becomes primary, loses its primary status, connects to a primary, or loses its connection to the primary. ``` { "type": "primaryChange", "data": { "isPrimary": false, "hostname": "myPrimaryHost:20202" } } ``` # LiteFS Export Command Source: https://docs.fly.io/litefs/export The litefs export command downloads a SQLite database from a local or remote LiteFS node with the -name and -url flags. It is safe on a live database. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview The `export` command will download a SQLite database from a LiteFS cluster. If the named database doesn't exist, it will return an error. This command is safe to use on a live database. ## Usage ```sh theme={null} litefs export [arguments] PATH ``` ### Arguments You'll need to specify a name for the database using the `-name` flag. ``` -name string database name ``` If you are exporting a database from a remote LiteFS node, you'll need to specify the base API URL for that node. This defaults to the local LiteFS node. ``` -url string LiteFS API URL (default "http://localhost:20202") ``` ## Examples ### Exporting from a local instance Export a database named `my.db` on LiteFS to the local `/path/to/db` path on disk: ```sh theme={null} litefs export -name my.db /path/to/db ``` ### Exporting from a remote instance Export a database named `my.db` on a remote LiteFS node to the local file path `/path/to/db`: ```sh theme={null} litefs export -name my.db -url http://myotherhost:20202 /path/to/db ``` # LiteFS FAQ Source: https://docs.fly.io/litefs/faq LiteFS answers on database size limits, FUSE write throughput near 100 tx/sec, async replication, write downtime on deploys, and LiteFS vs Litestream. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. LiteFS aims to work like running a single-server SQLite application as much as possible. However, since it is a distributed system, some things may work differently than you expect. This page is meant to provide answers for commonly asked questions about LiteFS, how it works, and how it differs from other similar database systems. ## What is LiteFS used for? Typically, LiteFS is used for replicating your application data to servers that are close to users. This has a significant impact on reducing latency as application servers can process read requests from their local, on-disk database rather than communicating with a database server over the network. It can also be used for replicating auxiliary databases to a fleet of servers so that changes on the primary are immediately visible to replicas. These databases can include configuration information or they can include supplemental data like geospatial lookups. ## What tool(s) does LiteFS replace? LiteFS works similarly to Postgres or MySQL streaming replication so it can be used as an alternative to those tools when you are using SQLite as your database. SQLite read queries are extremely fast so some users find that they no longer need tools such as Redis or memcached. ## What size databases can LiteFS handle? LiteFS doesn't have a hard limit for the size of individual SQLite databases. It is typically bound by network and disk I/O as well as the write throughput of the FUSE file system interface. In our testing we typically target SQLite databases that are 10GB or less as that makes up the majority of SQLite databases in real-world applications. Disk space is usually the limiting factor and we recommend that volumes have at least 20-50% extra disk space that LiteFS can use for storing temporary transaction files. So for a 10GB database, we recommend using a 12-15GB volume. ## How many databases can a LiteFS cluster handle? Each database requires a small amount of memory to track the current state on the primary and each subscribing replica. You should be able to use tens or hundreds of databases on LiteFS without an issue. We have done limited testing on a high number of databases so thousands or hundreds of thousands databases may pose a problem. ## What are the tradeoffs of using LiteFS? LiteFS' use of \[FUSE] ([https://en.wikipedia.org/wiki/Filesystem\_in\_Userspace](https://en.wikipedia.org/wiki/Filesystem_in_Userspace)) limits the write throughput to about 100 transactions per second so write-heavy applications may not be a good fit. The operating system automatically caches pages in memory so read queries typically don't see much of a slowdown compared to other file systems. We will be implementing a SQLite VFS implementation in the future which avoids FUSE and that will improve write speeds significantly. Compared to other replication tools, LiteFS has similar to tradeoffs to other asynchronous streaming replication methods. Asynchronous replication means that there can be a short window of data loss if the primary dies unexpectedly and a write has not been replicated to a replica yet. We will be supporting synchronous replication in the future. ## Are LiteFS deploys zero downtime? Rolling deploys of a LiteFS cluster will not experience any read availability loss but may have a short window of a few milliseconds of write availability loss. LiteFS can handoff the role of primary to another candidate node automatically but the primary changes can take a few milliseconds to propagate to replica nodes so they may redirect to the old primary during this short window. In the event of a sudden failure of the primary node, a different candidate node will acquire primary status via a distributed lease from Consul. By default, the lease has a time-to-live (TTL) of 10 seconds so you could lose write availability for up to that amount of time. Your database will still be readable by all LiteFS nodes during this time though. ## What's the difference between LiteFS & Litestream? [Litestream](https://litestream.io/) is intended as a single-node, disaster recovery tool. If you are only running a single server then it can be a great option. The main tradeoffs of using Litestream are that it cannot replicate data to other live servers and it does not support automatic failover. LiteFS was originally split off of Litestream in order to keep Litestream as a simple disaster recovery tool. LiteFS improves upon Litestream by adding live replication to replica servers and it provides failover by using [Consul](https://consul.io/) for distributed leases. When making a decision between the two tools, you'll typically choose Litestream for single-server deployments and LiteFS for multi-server deployments. # Getting Started with LiteFS in Docker Source: https://docs.fly.io/litefs/getting-started-docker Run a LiteFS cluster in Docker: install fuse3, copy the flyio/litefs binary, set a static lease in litefs.yml, use --privileged, route writes via nginx. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview This guide will walk you through the steps of getting a LiteFS cluster up and running using Docker. You can also use this guide as a reference for running LiteFS on a Linux server with minimal changes. If you're planning to run your app on Fly.io, please take a look at [Getting Started with LiteFS on Fly.io][Getting Started Fly] instead. For a full, working example of a LiteFS application, with a docker-compose setup that you can run locally, please see the [litefs-example][] repository. [litefs-example]: https://github.com/superfly/litefs-example [Getting Started Fly]: /litefs/getting-started-fly ## Adding LiteFS to your Dockerfile ### Dependencies The `litefs` binary is self-contained, but you'll need to install the `fuse3` library so LiteFS is able to mount a local file system. You'll also need `ca-certificates` if you're connecting to Consul, and you'll almost certainly want to install `sqlite`. This installation depends on your package manager, but here is a line you can add to your Dockerfile for alpine-based or debian-based images: ```dockerfile theme={null} # for alpine-based images RUN apk add ca-certificates fuse3 sqlite ``` ```dockerfile theme={null} # for debian/ubuntu-based images RUN apt-get update -y && apt-get install -y ca-certificates fuse3 sqlite3 ``` ### Installing LiteFS LiteFS is meant to run inside your container alongside your application. You can pull in the `litefs` binary by copying it from the official Docker image: ```dockerfile theme={null} COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs ``` It's recommended that you run LiteFS as `root` in Docker instead of using the `USER` command to change users. If you need to run your application as another user, use the `su` command to run your application as a non-root user. Take a look at [the example Dockerfile][Dockerfile] in the `litefs-example` repo for an example. [Dockerfile]: https://github.com/superfly/litefs-example/blob/main/Dockerfile ## Configuring LiteFS Most configuration options for LiteFS are set via a YAML configuration file called `litefs.yml`. This file is typically placed in `/etc/litefs.yml` but you can change the path by using the `-config` flag. You can take a look at [a complete example][LiteFS yml] of what your `litefs.yml` file should look like. [LiteFS yml]: https://github.com/superfly/litefs-example/blob/main/docker-config/etc/litefs.static-lease.yml ### File system Let's first set two fields to tell LiteFS where to mount its file system and where to store its internal data. ```yml theme={null} # This directory is where your application will access the database. fuse: dir: "/litefs" # This directory is where LiteFS will store internal data. # You must place this directory on a persistent volume. data: dir: "/var/lib/litefs" ``` ### Lease configuration LiteFS only allows a single node to be the *primary* at any given time. The primary node is the only one that can write data to the database. The other nodes are called *replicas* and they provide a read-only copy. The primary is determined by using a [*distributed lease*](https://martinfowler.com/articles/patterns-of-distributed-systems/time-bound-lease.html). In this guide, we'll be using a static lease, because it's simple to configure. You'll need two slightly different configurations for your primary and replica nodes. In particular, use `lease.candidate: true` in the primary node configuration, and `lease.candidate: false` in the replica node configuration. Here's an example of the primary node's `litefs.yml` file: ```yml theme={null} lease: # Required. Must be either "consul" or "static". type: "static" # Required. The URL for the primary node's LiteFS API. # Note: replace `primary` with the appropriate hostname for your primary node! advertise-url: "http://primary:20202" # Specifies whether the node can become the primary. If using # "static" leasing, this should be set to true on the primary # and false on the replicas. # Note: update this to `false` on the replica nodes! candidate: true ``` The replica node configuration should be identical, except with `lease.candidate` set to `false`. ## Running LiteFS The main command used to start LiteFS is the `litefs mount` command. This mounts a FUSE file system and then starts an API server for LiteFS nodes to communicate with each other. You can use this as the `ENTRYPOINT` in your Dockerfile: ```dockerfile theme={null} ENTRYPOINT litefs mount ``` ### Running as a supervisor LiteFS can either be run on its own or it can act as a simple supervisor process for your application. Running as a supervisor lets LiteFS wait to start the application until after it has connected to the cluster. You can specify one or more commands in the `exec` section of your config. If you set `lease.promote` to `true`, then you can specify to run your migration scripts only on candidate nodes. This means that candidates will automatically promote to the primary and run the migrations. ```yml theme={null} exec: # Only run migrations on candidate nodes. - cmd: "rails db:migrate" if-candidate: true # Then run the application server on all nodes. - cmd: "rails server" ``` ### Docker container privileges LiteFS uses the FUSE filesystem, which requires some additional privileges to run. The easiest way to get this working quickly is to run with `--privileged`: ```sh theme={null} docker run --privileged ... ``` Depending on your security requirements, you should take a look at the [FUSE documentation][] and decide whether you can reduce privileges! [FUSE documentation]: https://www.kernel.org/doc/html/next/filesystems/fuse.html ### Testing your LiteFS instance Once LiteFS is mounted, you can use SQLite clients or the `sqlite3` CLI to interact with databases on the mount directory: ``` sqlite3 /litefs/my.db ``` LiteFS only allows files in the root of the mount and it does not currently support subdirectories. ## Importing your database If you have an existing database, you can import it using the `litefs import` command. ```sh theme={null} litefs import -name my.db /path/to/database ``` Refer to the [`litefs import`](/litefs/import) documentation for more details. > [!WARNING] > > You should only interact with SQLite databases on LiteFS through > a SQLite client or through the `litefs` tooling. > > Do not use `cp` to copy a database into place. ## Configuring writes to primary node LiteFS has a few differences from regular SQLite since it is a distributed system. LiteFS requires that all writes occur on the primary node which means that applications need to redirect write requests to the current primary. It's also possible to issue a write to the primary and then read from a replica before the change is propagated to that replica. For most web applications, you can take advantage of load balancer configuration to route writes to the primary node, assuming your application follows the convention of avoiding write operations (`INSERT`, `UPDATE`, etc.) on `GET` requests. You can take a look at this [sample nginx config](https://github.com/superfly/litefs-example/blob/main/docker-config/nginx/nginx.conf) which routes writes to the primary node, and load balances between primary and replica node for other requests. ## Next steps [Back up your LiteFS cluster](/litefs/backup). # Getting Started with LiteFS on Fly.io Source: https://docs.fly.io/litefs/getting-started-fly Set up a LiteFS cluster on Fly.io: install litefs, configure litefs.yml with a volume, Consul lease, and proxy, then run litefs mount and import a database. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview This guide will walk you through the steps of getting a LiteFS cluster up and running on Fly.io. For a full, working example of a LiteFS application, please see the [litefs-example][] repository. [litefs-example]: https://github.com/superfly/litefs-example ## Installing LiteFS ### Dependencies The `litefs` binary is self-contained, but you'll need to install the `fuse3` library so LiteFS is able to mount a local file system. You'll also need `ca-certificates` if you're connecting to Consul, and you'll almost certainly want to install `sqlite`. This installation depends on your package manager, but here is a line you can add to your Dockerfile for alpine-based or debian-based images: ```dockerfile theme={null} # for alpine-based images RUN apk add ca-certificates fuse3 sqlite ``` ```dockerfile theme={null} # for debian/ubuntu-based images RUN apt-get update -y && apt-get install -y ca-certificates fuse3 sqlite3 ``` ### Installing LiteFS LiteFS is meant to run inside your container alongside your application. You can pull in the `litefs` binary by copying it from the official Docker image: ```Docker theme={null} COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs ``` It's recommended that you run LiteFS as `root` in Docker instead of using the `USER` command to change users. If you need to run your application as another user, use the `su` command to run your application as a non-root user. Take a look at [the example Dockerfile][Dockerfile] in the `litefs-example` repo for an example. [Dockerfile]: https://github.com/superfly/litefs-example/blob/main/Dockerfile ## Configuring LiteFS Most configuration options for LiteFS are set via a YAML configuration file called `litefs.yml`. This file is typically placed in `/etc/litefs.yml`, but you can change the path by using the `-config` flag. Take a look at [the litefs.yml file](https://github.com/superfly/litefs-example/blob/main/fly-io-config/etc/litefs.yml) in the `litefs-example` repo for a full example, or follow along with the rest of this section for a more detailed explanation! [litefs yml file]: https://github.com/superfly/litefs-example/blob/main/fly-io-config/etc/litefs.yml ### File system Let's first set two fields to tell LiteFS where to mount its file system and where to store its internal data. ```yml theme={null} # This directory is where your application will access the database. fuse: dir: "/litefs" # This directory is where LiteFS will store internal data. # You must place this directory on a persistent volume. data: dir: "/var/lib/litefs" ``` If you're running on Fly.io, you should create a volume: ```sh theme={null} fly volumes create litefs --size 10 ``` And then specify it as a mount in your `fly.toml`: ```toml theme={null} [mounts] source = "litefs" destination = "/var/lib/litefs" ``` ### Lease configuration LiteFS only allows a single node to be the *primary* at any given time. The primary node is the only one that can write data to the database. The other nodes are called *replicas* and they provide a read-only copy. The primary is determined by using a [*distributed lease*](https://martinfowler.com/articles/patterns-of-distributed-systems/time-bound-lease.html). In this guide, we'll be using a [Consul][] lease as it allows the primary to automatically failover in order to have high write availability. You can add a Consul URL to your app with: ``` fly consul attach ``` That will set a `FLY_CONSUL_URL` secret for the app, which will have the cluster url. Then in your `litefs.yml`, set the `lease` section: ```yml theme={null} lease: type: "consul" # Specifies if this node can become primary. The expression below evaluates # to true on nodes that are run in the primary region. Nodes in other regions # act as non-candidate, read-only replicas. candidate: ${FLY_REGION == PRIMARY_REGION} # If true, then the node will automatically become primary after it has # connected with the cluster and sync'd up. This makes it easier to run # migrations on start up. promote: true # The API URL that other nodes will use to connect to this node. advertise-url: "http://${FLY_ALLOC_ID}.vm.${FLY_APP_NAME}.internal:20202" consul: # The URL of the Consul cluster. url: "${FLY_CONSUL_URL}" # A unique key shared by all nodes in the LiteFS cluster. # Change this if you are running multiple clusters in a single app! key: "${FLY_APP_NAME}/primary" ``` You can find more details in the [lease management][] section of the configuration guide. [Consul]: https://consul.io [fly.io]: https://fly.io [lease management]: /litefs/config#lease-management ### Configuring the proxy LiteFS requires that all writes occur on the primary node, which means that applications need to redirect write requests to the current primary. It's also possible to issue a write to the primary and then read from a replica before the change is propagated to that replica. Most web applications can take advantage of a thin, built-in proxy inside LiteFS that automatically handles these write redirection and replica consistency issues. In order to make use of this proxy, your application needs to follow these rules: * `GET` requests never perform write operations (e.g. `INSERT`, `UPDATE`, etc). * Clients have cookies enabled. To configure the proxy, you'll need to set the `proxy` section in the config file: ```yml theme={null} proxy: # Bind address for the proxy to listen on. addr: ":8080" # Hostport of your application - replace 8081 with whatever port # your application is listening on! target: "localhost:8081" # Filename of the SQLite database you want to use for TXID tracking. db: "my.db" ``` You can find more details on the [proxy configuration guide][]. [proxy configuration guide]: /litefs/proxy ## Running LiteFS The main command used to start LiteFS is the `litefs mount` command. This mounts a FUSE file system and then starts an API server for LiteFS nodes to communicate with each other. You can use this as the `ENTRYPOINT` in your Dockerfile: ```dockerfile theme={null} ENTRYPOINT litefs mount ``` ### Running as a supervisor LiteFS can either be run on its own or it can act as a simple supervisor process for your application. Running as a supervisor lets LiteFS wait to start the application until after it has connected to the cluster. You can specify one or more commands in the `exec` section of your config. If you set `lease.promote` to `true`, then you can specify to run your migration scripts only on candidate nodes. This means that candidates will automatically promote to the primary and run the migrations. ```yml theme={null} exec: # Only run migrations on candidate nodes. - cmd: "rails db:migrate" if-candidate: true # Then run the application server on all nodes. - cmd: "rails server" ``` ### Testing your LiteFS instance Once LiteFS is mounted, you can use SQLite clients or the `sqlite3` CLI to interact with databases on the mount directory: ``` sqlite3 /litefs/my.db ``` LiteFS only allows files in the root of the mount and it does not currently support subdirectories. ## Importing your database If you have an existing database, you can import it using the `litefs import` command. ```sh theme={null} litefs import -name my.db /path/to/database ``` Refer to the [`litefs import`](/litefs/import) documentation for more details. > [!WARNING] > > You should only interact with SQLite databases on LiteFS through > a SQLite client or through the `litefs` tooling. > > > [!WARNING] > > > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. > > Do not use `cp` to copy a database into place. ## Next steps [Back up your LiteFS cluster](/litefs/backup). # How LiteFS Works Source: https://docs.fly.io/litefs/how-it-works LiteFS intercepts SQLite journal and WAL writes over FUSE, packs page sets into LTX files, tracks TXID positions, and elects a primary with Consul leases. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. LiteFS is a distributed file system specifically built for replicating SQLite databases. This allows each application node to have a full, local copy of the database and respond to requests with minimal latency. LiteFS is built for high availability so that operations can continue even in the event of catastrophic failures. ## Capturing SQLite Transactions SQLite works by updating a subset of pages in the database file on every transaction. It does this by using the file system API and the exact steps depend on whether you're using a rollback journal or a write-ahead log. LiteFS acts as a passthrough file system which intercepts these API calls and copies out the page sets for each transaction. These page sets are stored in an internal format called [LTX](https://github.com/superfly/ltx) that performs extensive consistency checking to ensure correctness. By applying these page sets in the LTX files in order, we can reconstruct the state of our SQLite database at any point in time. We can also copy them to other nodes to replicate the changes in real time to our cluster. ### Rollback journal handling The following steps occur when SQLite creates a transaction with either the `DELETE`, `PERSIST`, or `TRUNCATE` journal modes: 1. Obtain an exclusive lock on the `RESERVED` byte. 2. Create a journal file to start the transaction. 3. Update page in the main database file while copying old pages to the journal. 4. Invalidate the journal file to finish the transaction. 5. Release the lock on the `RESERVED` byte. LiteFS maintains a set of dirty database pages based on `write(2)` calls to the database file. When the journal file is invalidated, LiteFS will copy out the dirty pages in sequential order to an LTX transaction file. ### Write-ahead log (WAL) handling The following steps occur when SQLite creates a transaction with the `WAL` journal mode: 1. Acquire an exclusive lock on the `WAL_WRITE_LOCK`. 2. Write a WAL frame to the end of the WAL for each new or updated page. 3. On commit, write the new size of the database to the `commit` field in the last WAL frame header. 4. Release the exclusive lock on the `WAL_WRITE_LOCK`. Because all page changes are in the write-ahead log, LiteFS does not need to maintain a dirty page set. Instead, it reads from the previous ending offset of the WAL to the end to capture all new & changed pages into an LTX file. ## Replication Position Each database within the LiteFS directory contains a replication position. This position is a combination of the transaction ID (TXID) and the rolling checksum of the database contents. The TXID is an integer that is incremented with every write transaction that occurs. You can track the relative position between nodes by reading from the database's position file in the LiteFS mount directory. For example, if you have a database called `/mnt/db` then you can read the position from `/mnt/db-pos` file: ```sh theme={null} $ cat /mnt/db-pos 00000000000003e8/a3b2b72f1147c9bc ``` The output is a hex-encoded TXID followed by a slash and then the database rolling checksum. ## Cluster management using leases SQLite operates as a single-writer database which means only one transaction can write at a time. Because of this, we use a lease system whereby a single node acts as the "primary" node at any given time. All writes should be directed to that node and it will propagate changes to the rest of the cluster. However, nodes can die or become disconnected from the cluster so the primary must be able to change dynamically. LiteFS is built to be run on ephemeral environments where cluster membership can change quickly and frequently so it utilizes leases via Consul to determine the current primary node. When a LiteFS node starts up, it checks a key within the Consul server for the current primary node. If no primary node exists, then the LiteFS node attempts to obtain a lease from Consul to become a primary node itself. If successful, it updates the Consul key to share its API URL so other LiteFS nodes can connect to it. ### Handling split brain LiteFS uses *asynchronous replication* which means that it does not confirm that writes are committed to other nodes before continuing to the next transaction. This tradeoff is made to increase write throughput at the cost of some durability. In the event of an unexpected primary node failure, the primary node may become out of sync with the rest of the cluster if it has unreplicated writes it has applied locally but have not been copied to the other nodes. If a new primary is elected then the old primary could corrupt its data if it tried to apply transactions from the new primary. Fortunately, LiteFS maintains a rolling checksum of the full contents of the database so it will detect this split and automatically snapshot from the new primary to the out-of-sync node. Synchronous replication & time-bound asynchronous replication are features that are planned for future development. # LiteFS Import Command Source: https://docs.fly.io/litefs/import Upload a SQLite file to a LiteFS cluster with litefs import -name, creating or replacing it on a local or remote node. Run PRAGMA integrity_check first. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview The `import` command will upload a SQLite database to a LiteFS cluster. If the named database doesn't exist, it will be created. If it does exist, it will be replaced. This command is safe to use on a live database. The database file is not validated for integrity by LiteFS. You can perform an integrity check first by running "PRAGMA integrity\_check" from the SQLite CLI. ## Usage ```sh theme={null} litefs import [arguments] PATH ``` ### Arguments You'll need to specify a name for the database using the `-name` flag. This is the filename that your application will use to access the database. ``` -name string database name ``` If you are importing a database to a remote LiteFS node, you'll need to specify the base API URL for that node. This defaults to the local LiteFS node. ``` -url string LiteFS API URL (default "http://localhost:20202") ``` ## Examples ### Importing to a local instance Import a database from `/path/to/db` to a database called `my.db` on a local LiteFS node: ```sh theme={null} litefs import -name my.db /path/to/db ``` ### Importing to a remote instance Import a database from `/path/to/db` to a database called `my.db` on a remote LiteFS node with a hostname of `myotherhost`: ```sh theme={null} litefs import -name my.db -url http://myotherhost:20202 /path/to/db ``` # LiteFS - Distributed SQLite Source: https://docs.fly.io/litefs/index Replicate SQLite across nodes with LiteFS, a distributed file system, on Fly.io or with Docker. Find the getting started guides and the no-support warning. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent 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 on-disk SQLite database but behind the scenes the database is replicated to all the nodes in your cluster. With LiteFS, you can run your database right next to your application on the edge. You can run LiteFS anywhere! ## Project status 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](/litefs/backup) in case of malfunction or disk corruption. ## Exploring our guides You can get up and running quickly with one of our guides: * [Speedrun: Adding LiteFS to your app](/litefs/speedrun) the fastest way to get started with LiteFS on Fly.io. * [Getting Started on Fly.io](/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](/litefs/getting-started-docker) helps you add LiteFS to an existing application that you want to run outside of Fly.io. * [How LiteFS Works](/litefs/how-it-works) explains the concepts behind LiteFS. # Running database migrations Source: https://docs.fly.io/litefs/migrations Run migrations on LiteFS with lease.promote auto-promotion and if-candidate exec commands, or forward writes from any node with -with-halt-lock-on. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview LiteFS is a single-writer system so only the primary node can write to the database. When you deploy a new version of your application, you will likely need to run a database migration but the first node you deploy to won't necessarily be the primary node. There are two options for performing migrations when a node starts up: auto-promotion & write forwarding. ## Auto-promotion The recommended way to run migrations when you deploy is to have candidate nodes automatically promote themselves after they've connected to the cluster and are sync'd up. This can be done by setting the `lease.promote` field to `true` in the configuration and then running your migrations as one of your `exec` commands. ```yml theme={null} lease: candidate: ${FLY_REGION == PRIMARY_REGION} promote: true exec: - cmd: "rails db:migrate" if-candidate: true - cmd: "rails server" ``` In this example `litefs.yml` config, we are marking nodes whose region (`FLY_REGION`) is equal to the primary region of the app (`PRIMARY_REGION`). On startup, they will request a primary handoff from the current primary node and become the primary themselves. Once they are promoted, LiteFS will execute the `rails db:migrate` command on the node if it is a candidate. Then it will run `rails server` on all nodes. Your migrations must be idempotent as they will be run on each candidate node. Your application must also handle running against the next version of the database schema as replica nodes can receive database updates before a new deployment of the app has completed. ### Using the `run` command You can also perform migrations separately from your deploy by logging into a candidate node and using `litefs run` with the `-promote` command: ```sh theme={null} litefs run -promote -- /path/to/my_migration.sh ``` This will perform a primary handoff before running the given script. ## Write forwarding All LiteFS nodes also have the ability to halt writes on the primary node and temporarily perform writes on the local node. These changes are then forwarded back to the primary node and propagated out to the rest of the cluster. To perform a remote write, you can use the `litefs run` command and the `-with-halt-lock-on` flag to specify the database you wish to lock. ```sh theme={null} litefs run -with-halt-lock-on /litefs/db -- bin/rails db:migrate ``` This command is meant for small, infrequent transactions as it is slower than writing to the primary node. Please note that the `HALT` lock is limited to 30 seconds before it is automatically released to ensure that replicas do not permanently lock the primary. You can find a reference implementation of a client library in the [litefs-go][] repository. [litefs-go]: https://github.com/superfly/litefs-go # LiteFS Mount Command Source: https://docs.fly.io/litefs/mount litefs mount starts the FUSE mount and joins the cluster. Covers the -config and -no-expand-env flags plus -fuse.debug and -tracing for verbose debug logs. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview The `mount` command will mount a LiteFS directory via FUSE and begin communicating with the LiteFS cluster. The mount will be accessible once the node becomes the primary or is able to connect and sync with the primary. Most options are specified in the [litefs.yml config file](/litefs/config). ## Usage ```sh theme={null} litefs mount [arguments] ``` ### Configuration A specific config file path can be specified using the `-config` flag. If the config file cannot be read from this path, LiteFS will exit with an error and not attempt to search the other config paths. Environment variable expansion can be disabled via the `-no-expand-env` flag. ``` -config string config file path -no-expand-env do not expand env vars in config ``` ### Debugging These debugging flags can produce verbose output so they are not recommended for regular use. The `-fuse.debug` flag enables logging for every FUSE request and response. This can also be enabled via the config file. The `-tracing` flag enables logging for LiteFS-specific events such as reading and writing database pages or acquiring and releasing locks. This flag enables output to `STDOUT` but a disk-based tracing log can be enabled via the config file. ``` -fuse.debug enable FUSE debug logging -tracing enable trace logging to stdout ``` # Tracking LiteFS replication position Source: https://docs.fly.io/litefs/position Read a database's -pos file for its LiteFS replication position, a hex TXID plus rolling checksum, and use it or a delay for read-your-writes consistency. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. Each database created in LiteFS maintains a position in their replication log. The position is a combination of a monotonically incrementing transaction ID (TXID) and a rolling checksum of the database contents. You can view the position for any database by reading the `-pos` suffixed file next to the database file. For example, if you have a database named `db` then you can read from a file called `db-pos`: ```sh theme={null} $ cat /path/to/mnt/db-pos 00000000002df417/ce552e44f23fbbdd ``` The TXID is represented a 16-character hex-encoded `uint64`. The checksum is encoded as a 16-character hex-encoded `uint64`. These are concatenated using a slash and a newline is appended at the end. You can also use the [LiteFS event stream](./events) to receive replication position updates in real time. ### Using TXID to handle consistency Clients can use the TXID if they wish to ensure that the database state is consistent across requests to different nodes. For example, a write to the primary may occur at TXID 100. If a subsequent request to a replica is performed, then the client can pass the TXID to the application and have it wait until it reaches or surpasses transaction ID 100. ### Using a time-delay to handle consistency Another approach is to redirect subsequent reads from a client to the primary for a few seconds after a write occurs. This is typically enough time to ensure that changes from the write are copied to the replica before it begins serving read requests to the client again. However, this provides weaker consistency guarantees over checking with the TXID in the position file since replication is not guaranteed to occur within a specified time frame. # Determining the LiteFS primary Source: https://docs.fly.io/litefs/primary Read the .primary file in the LiteFS mount to get the primary's hostname, decide when to forward writes, and see why replicas return SQLITE_READONLY. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. The LiteFS directory (typically `/litefs`) provides a `.primary` file that applications can read to determine the current primary node. This file only exists if the node is currently a replica *and* it is connected to the primary LiteFS node. You can use `cat(1)` to view the contents on a replica node: ```sh theme={null} $ cat /litefs/.primary hostname.example.com ``` This file will return the `hostname` specified in the primary node's configuration file or, if unspecified, it will read `hostname(2)` from the primary node's operating system. A newline is appended to the end. If the `.primary` file is unavailable then the local LiteFS node is either: 1. Currently the primary and can accept writes, or 2. Unable to determine or connect to the primary. When a node receives a write request, it should check the `.primary` file and, if it exists, it should forward the request to the primary. If the file does not exist, the request should attempt to write locally. If the local node is not the primary then SQLite will return a `SQLITE_READONLY` error when using the rollback journal and it will return a `disk I/O error` when using the write-ahead log. Unfortunately, there's not a better error that LiteFS can return to SQLite when using the WAL. # Built-in HTTP Proxy Source: https://docs.fly.io/litefs/proxy The LiteFS HTTP proxy on Fly.io forwards writes to the primary via fly-replay and holds replica reads until a TXID cookie catches up. No WebSocket support. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. ## Overview > [!NOTE] > > You can only use the built-in HTTP proxy described here for running LiteFS on Fly.io. If you're running LiteFS > outside of Fly.io, you will need to use an alternative method for > [configuring writes to the primary node](/litefs/getting-started-docker#configuring-writes-to-primary-node)! LiteFS uses a single primary node for writing data and that primary node replicates out to other replica nodes for fast reads. This poses two issues for applications: 1. Requests that write data must be forwarded to the primary. 2. Requests that only read data should wait until previous writes have been replicated to a node before reading from that node. LiteFS provides a built-in HTTP proxy server that can handle these issues automatically for most web applications as long as they follow some common idioms: * `GET` requests should not perform write operations (e.g. `INSERT`, `UPDATE`, etc). * Clients must have cookies enabled. ## How it works The built-in proxy is a thin layer in front of your application. It inspects HTTP requests and performs intelligent routing depending on the type of request and the node's status. All HTTP requests that go to the primary are handled normally since the primary node can perform writes and always has the latest data. The proxy simply adds a cookie to the outgoing response from the primary to tag the current replication position. The proxy handles requests a bit differently on replicas since they cannot write data and they may lag slightly behind the primary. First, write requests (`POST`, `PUT`, etc) are forwarded to primary node using [`fly-replay` header](/networking/dynamic-request-routing). This ensures that all writes occur on the primary. For read requests (`GET`), the replica will wait for the replication position on the database to catch up to the position stored in the cookie set by the primary server. This ensures that any writes forwarded to the primary are visible by the replicas before processing the request. ## Configuring the proxy By default, the proxy is disabled. You can enable it by setting fields in the [proxy section](/litefs/config#http-proxy-server) of the config: ```yml theme={null} proxy: # Bind address for the proxy to listen on. addr: ":8080" # Hostport of your application. target: "localhost:8081" # Filename of the SQLite database you want to use for TXID tracking. db: "my.db" ``` You'll also need to ensure that your application is running on the target port, which is `8081` in this example. Additionally, make sure your [internal port](/reference/configuration#the-services-sections) is configured to the proxy's bind port in your `fly.toml`: ```toml theme={null} [[services]] internal_port = 8080 protocol = "tcp" ``` ## Websockets At this time, the proxy does not work with WebSockets. You can still use LiteFS with WebSocket applications but you will need to internally proxy the write requests to the [current primary in the cluster](./primary). # LiteFS Releases Source: https://docs.fly.io/litefs/releases Download prebuilt LiteFS binaries for amd64 or arm64 from GitHub releases when running on a Linux VPS without Docker or skipping the LiteFS Docker image. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. If you're running on a Linux VPS without Docker, or as an alternative to installing from LiteFS Docker builds, you can download the latest version from our [LiteFS releases page](https://github.com/superfly/litefs/releases). LiteFS can run on either `amd64` or `arm64` architectures. # LiteFS Run Command Source: https://docs.fly.io/litefs/run litefs run executes the command after the double dash, with -promote to make the node primary, -if-candidate, and -with-halt-lock-on for write forwarding. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. ## Overview The `run` command will execute the program listed after the double dash. It provides options for promoting the current node or for performing write forwarding using the `HALT` lock. See the [migrations guide][] for details about promotion & write forwarding. [migrations guide]: /litefs/migrations ## Usage ```sh theme={null} litefs run [arguments] -- CMD [ARGS...] ``` ### Arguments ``` -if-candidate only execute if node is a candidate -promote promote node to primary -with-halt-lock-on string full database path to halt ``` ## Examples Promote the local node to become the primary and then run Rails migrations: ```sh theme={null} litefs run -promote -- bin/rails db:migrate ``` Run Rails migrations on the `/litefs/db` database from any node and have those write forwarded back to the primary node: ```sh theme={null} litefs run -with-halt-lock-on /litefs/db -- bin/rails db:migrate ``` # Speedrun: Add LiteFS to your app Source: https://docs.fly.io/litefs/speedrun Add LiteFS to a Dockerized app on Fly.io fast: copy the sample litefs.yml, install fuse3, create a volume, run fly consul attach, deploy, clone regions. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. This guide will get your app up and running with LiteFS on Fly.io as fast as possible. If you run into any issues with this guide, or you'd like some more details or a slower pace, check out the [Getting Started doc][] instead. You can also take a look at [the docs for your framework][Framework docs] to see if LiteFS is supported by `fly launch`. > [!NOTE] > > This guide assumes you already have a working `Dockerfile` for > your app. If not, get that working (with regular sqlite), and then come > back here! [Getting Started doc]: /litefs/getting-started-fly [Framework docs]: /languages-and-frameworks ## Let's do this! ### 1. Configure litefs.yml Copy [the sample `litefs.yml` file](https://github.com/superfly/litefs-example/blob/main/fly-io-config/etc/litefs.yml) from the `litefs-example` repo, and make the following updates: * Update from `8081` in `proxy.target` to whatever port your app listens on * Update `exec[0].cmd` to the command that runs your app ### 2. Update your Dockerfile Install LiteFS dependencies: ```dockerfile theme={null} # for alpine-based images RUN apk add ca-certificates fuse3 sqlite # or for debian/ubuntu-based images RUN apt-get update -y && apt-get install -y ca-certificates fuse3 sqlite3 ``` Copy in the LiteFS binary: ```dockerfile theme={null} COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs ``` Update the `ENTRYPOINT` (or `CMD`): ```dockerfile theme={null} ENTRYPOINT litefs mount ``` ### Set up & deploy to the Fly Platform Create a volume: ```sh theme={null} fly volumes create litefs --size 10 ``` Create your app config, but don't deploy it (say no when it asks): ```sh theme={null} fly launch ``` Configure consul (this sets the `FLY_CONSUL_URL` secret for your app, which is required for LitefS leases): ```sh theme={null} fly consul attach ``` Update your `fly.toml` file to mount your volume: ```yml theme={null} [mounts] source = "litefs" destination = "/var/lib/litefs" ``` Deploy your app: ```sh theme={null} fly deploy ``` TADA! You (hopefully) now have an app running with LiteFS! If you're having trouble, you can ask for help in [the Fly community](https://community.fly.io). ### 4. Add some replicas in other regions LiteFS isn't *super* useful if you're running only one node. So, add some more nodes in more regions! ```sh theme={null} # Add a clone in London fly m clone --select --region lhr # Add a clone in Sydney fly m clone --select --region syd ``` ## Next steps [Back up your LiteFS cluster](/litefs/backup). # Apps Source: https://docs.fly.io/machines/api/apps-resource Machines API Apps resource: create, get, list by org_slug, and delete Fly Apps, plus allocating IPs, setting secrets, and isolated app networks. You can use the Apps resource to create and manage Fly Apps. A Fly App is an abstraction for a group of Machines running your code, along with the configuration, provisioned resources, and data we need to keep track of to run and route to your Machines. Learn more about [Fly Apps](/apps/overview). Endpoints [POST /v1/apps](#create-a-fly-app) [GET /v1/apps/\{app\_name}](#get-app-details) [DELETE /v1/apps/\{app\_name}](#delete-a-fly-app) [GET /v1/apps?org\_slug=](#list-apps-in-an-organization) ## Create a Fly App `POST /apps` Machines must be associated with a Fly App. App names must be unique. #### Body parameters **app\_name** `string` *required* Name of the app to create **enable\_subdomains** `boolean` Used for Fly Kubernetes. **org\_slug** `string` *required* Slug of the org in which to create the app **network** `string` Name for an IPv6 private network to segment the app onto. ```sh POST/v1/apps theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps" \\ -d '{ "app_name": "my-app-name", "org_slug": "personal", "network": "my-optional-network" }' ``` ```json title="Status: 201 created - Example response" theme={null} { "id":"z4k69dxd8r31p5mx", "created_at":1708631799000 } ``` To segment the app into its own network, you can pass a `network` argument in the JSON body, for example: `"network": "some-arbitrary-name"`. Any Machine started in such an app will not be able to access other apps within their organization over the private network. However, Machines within such an app can communicate to each other, and the [`fly-replay`](/networking/dynamic-request-routing) header can still be used to route requests to Machines within a segmented app. ## Allocate an IP address for global request routing If you intend for Machines to be accessible to the internet, you’ll need to allocate an IP address to the app. Currently this is done using `flyctl` or the [Fly.io GraphQL API](https://api.fly.io/graphql). This offers your app automatic, global routing via [Anycast](/networking/services#about-anycast). Read more about this in [the Networking section](/machines/api/machines-resource#notes-on-networking). Example: ```sh cmd theme={null} fly ips allocate-v4 -a my-app-name ``` ```output output theme={null} TYPE ADDRESS REGION CREATED AT v4 37.16.9.52 global 7s ago ``` The app will answer on this IP address, and after a small delay, at `my-app-name.fly.dev`. ## Get app details `GET /apps/{app_name}` Get details about an app, like its organization slug and name. Also, to check if the app exists! #### Path parameters **app\_name** `string` *required* The name of the Fly App to get the details of. ```sh title="GET/v1/apps/{app_name}"" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name" ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "jlyv9r5d56v18xrg", "name": "my-app-name", "status": "pending", "organization": { "name": "My Org", "slug": "personal" } } ``` ## Set app secrets For sensitive environment variables, such as credentials, you can set [secrets](/apps/secrets/) on the app: ```sh theme={null} fly secrets set DATABASE_URL=postgres://example.com/mydb ``` Machines inherit secrets from the app. Existing Machines must be [updated](/machines/api/machines-resource#update-a-machine) to pick up secrets set after the Machine was created. For non-sensitive information, you can configure environment variables per Machine when you create or update the Machine. ## Delete a Fly App `DELETE /apps/{app_name}` Machines should be stopped before attempting deletion. Append `?force=true` to the URI to stop and delete immediately. #### Path parameters **app\_name** `string` *required* The name of the Fly App to delete. #### Query parameters **force** `Boolean` Stop all Machines and delete the app immediately. ```sh title="DELETE/v1/apps/{app_name}" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name" ``` ```txt title="Status: 202 accepted" theme={null} no body ``` ## List apps in an organization `GET /apps?org_slug=` #### Query parameters **org\_slug** `string` *required* The organization to list apps for. ```sh title="GET/v1/apps?org_slug=" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps?org_slug=personal" ``` ```json title="Status: 200 OK - Example response" theme={null} { "total_apps": 4, "apps": [ { "id": "682kqp6pdno9d543", "name": "my-app-1", "machine_count": 3, "volume_count": 2, "network": "default" }, ... ] } ``` ## Related topics * [Working with the Machines API](/machines/api/working-with-machines-api/) * [Machines resource](/machines/api/machines-resource/) reference * [Tokens resource](/machines/api/tokens-resource) reference * [Volumes resource](/machines/api/volumes-resource/) reference # Certificates Source: https://docs.fly.io/machines/api/certificates-resource Manage custom domain certificates via the Machines API: request ACME certs, import PEM certs, check DNS validation, and delete certificates. Use the Certificates resource to manage SSL/TLS certificates for custom domains on your Fly Apps. Fly.io can automatically issue Let's Encrypt certificates (ACME), or you can import your own custom certificates. When both exist for a hostname, the custom certificate is served as primary and the ACME certificate acts as an automatic fallback. Learn more about [custom domains](/networking/custom-domain/). Endpoints [GET /v1/apps/\{app\_name}/certificates](#list-certificates) [POST /v1/apps/\{app\_name}/certificates/acme](#request-acme-certificate) [POST /v1/apps/\{app\_name}/certificates/custom](#import-custom-certificate) [GET /v1/apps/\{app\_name}/certificates/\{hostname}](#get-certificate-details) [POST /v1/apps/\{app\_name}/certificates/\{hostname}/check](#check-certificate-status) [DELETE /v1/apps/\{app\_name}/certificates/\{hostname}](#delete-hostname-and-all-certificates) [DELETE /v1/apps/\{app\_name}/certificates/\{hostname}/acme](#delete-acme-certificates) [DELETE /v1/apps/\{app\_name}/certificates/\{hostname}/custom](#delete-custom-certificate) ## List certificates `GET /apps/{app_name}/certificates` List all certificates for an app, with optional filtering and cursor-based pagination. #### Path parameters **app\_name** `string` *required* The name of the Fly App. #### Query parameters **filter** `string` Filter certificates by hostname substring. **cursor** `string` Pagination cursor from a previous response. **limit** `integer` Maximum number of certificates to return. ```sh title="GET/v1/apps/{app_name}/certificates" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates" ``` ```json title="Status: 200 OK - Example response" theme={null} { "certificates": [ { "hostname": "example.com", "status": "active", "dns_provider": "enom", "acme_dns_configured": true, "acme_alpn_configured": true, "acme_http_configured": true, "ownership_txt_configured": true, "configured": true, "acme_requested": true, "has_custom_certificate": false, "has_fly_certificate": true, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T12:00:00Z" } ], "total_count": 1 } ``` ## Request ACME certificate `POST /apps/{app_name}/certificates/acme` Add a hostname to your app and request an automatic Let's Encrypt certificate. Fly.io will attempt to validate domain ownership and issue a certificate. Check the `dns_requirements` in the response to see what DNS records you need to configure. #### Path parameters **app\_name** `string` *required* The name of the Fly App. #### Body parameters **hostname** `string` *required* The domain to issue a certificate for. ```sh title="POST/v1/apps/{app_name}/certificates/acme" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/acme" \\ -d '{ "hostname": "example.com" }' ``` ```json title="Status: 201 created - Example response" theme={null} { "hostname": "example.com", "configured": false, "acme_requested": true, "status": "pending_validation", "dns_provider": "enom", "rate_limited_until": null, "certificates": [], "validation": { "dns_configured": false, "alpn_configured": false, "http_configured": false, "ownership_txt_configured": false }, "dns_requirements": { "a": ["137.66.XXX.XXX"], "aaaa": ["2a09:8280:1::XXXX"], "cname": "my-app-name.fly.dev", "acme_challenge": { "name": "_acme-challenge.example.com", "target": "example.com.XXXXX.flydns.net" }, "ownership": { "name": "_fly-ownership.example.com", "app_value": "app-XXXXXXXXXX", "org_value": "org-XXXXXXXXXX" } }, "validation_errors": [] } ``` ## Import custom certificate `POST /apps/{app_name}/certificates/custom` Upload your own certificate and private key in PEM format. The certificate must not be expired, the private key must match the certificate, and the hostname must appear in the certificate's Subject Alternative Names (SAN) or Common Name (CN). Wildcard certificates are supported. Domain ownership must be verified via a `_fly-ownership` DNS TXT record before the certificate becomes active. Check the `dns_requirements.ownership` field in the response for the required record. #### Path parameters **app\_name** `string` *required* The name of the Fly App. #### Body parameters **hostname** `string` *required* The domain for the certificate. **fullchain** `string` *required* PEM-encoded certificate chain. **private\_key** `string` *required* PEM-encoded private key. ```sh title="POST/v1/apps/{app_name}/certificates/custom" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/custom" \\ -d '{ "hostname": "example.com", "fullchain": "-----BEGIN CERTIFICATE-----\\nMIID...\\n-----END CERTIFICATE-----", "private_key": "-----BEGIN PRIVATE KEY-----\\nMIIE...\\n-----END PRIVATE KEY-----" }' ``` ```json title="Status: 201 created - Example response" theme={null} { "hostname": "example.com", "configured": false, "acme_requested": false, "status": "pending_ownership", "dns_provider": "enom", "rate_limited_until": null, "certificates": [ { "source": "custom", "status": "pending_ownership", "created_at": "2024-01-15T10:30:00Z", "expires_at": "2039-01-15T10:30:00Z", "issuer": "Cloudflare Origin Certificate", "issued": [] } ], "validation": { "dns_configured": false, "alpn_configured": false, "http_configured": false, "ownership_txt_configured": false }, "dns_requirements": { "a": ["137.66.XXX.XXX"], "aaaa": ["2a09:8280:1::XXXX"], "cname": "my-app-name.fly.dev", "acme_challenge": { "name": "_acme-challenge.example.com", "target": "example.com.XXXXX.flydns.net" }, "ownership": { "name": "_fly-ownership.example.com", "app_value": "app-XXXXXXXXXX", "org_value": "org-XXXXXXXXXX" } }, "validation_errors": [] } ``` ## Get certificate details `GET /apps/{app_name}/certificates/{hostname}` Get detailed information about a hostname's certificates, including validation status, DNS requirements, and any validation errors. For wildcard hostnames, URL-encode the `*` character (e.g. `%2A.example.com`). #### Path parameters **app\_name** `string` *required* The name of the Fly App. **hostname** `string` *required* The hostname to get certificate details for. ```sh title="GET/v1/apps/{app_name}/certificates/{hostname}" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com" ``` ```json title="Status: 200 OK - Example response" theme={null} { "hostname": "example.com", "configured": true, "acme_requested": true, "status": "active", "dns_provider": "enom", "rate_limited_until": null, "certificates": [ { "source": "custom", "status": "active", "created_at": "2024-01-15T10:30:00Z", "expires_at": "2039-01-15T10:30:00Z", "issuer": "Cloudflare Origin Certificate", "issued": [] }, { "source": "fly", "status": "active", "created_at": "2024-01-15T12:00:00Z", "expires_at": "2024-04-15T12:00:00Z", "issuer": null, "issued": [ { "type": "ecdsa", "expires_at": "2024-04-15T12:00:00Z", "certificate_authority": "lets_encrypt" } ] } ], "validation": { "dns_configured": true, "alpn_configured": true, "http_configured": true, "ownership_txt_configured": true }, "dns_requirements": { "a": ["137.66.XXX.XXX"], "aaaa": ["2a09:8280:1::XXXX"], "cname": "my-app-name.fly.dev", "acme_challenge": { "name": "_acme-challenge.example.com", "target": "example.com.XXXXX.flydns.net" }, "ownership": { "name": "_fly-ownership.example.com", "app_value": "app-XXXXXXXXXX", "org_value": "org-XXXXXXXXXX" } }, "validation_errors": [] } ``` ## Check certificate status `POST /apps/{app_name}/certificates/{hostname}/check` Trigger a fresh DNS validation check for a hostname. Returns the same details as the get endpoint, plus actual DNS records resolved for the hostname. Use this to diagnose DNS configuration issues. #### Path parameters **app\_name** `string` *required* The name of the Fly App. **hostname** `string` *required* The hostname to check. ```sh title="POST/v1/apps/{app_name}/certificates/{hostname}/check" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com/check" ``` ```json title="Status: 200 OK - Example response" theme={null} { "hostname": "example.com", "configured": true, "acme_requested": true, "status": "active", "dns_provider": "enom", "rate_limited_until": null, "certificates": [ { "source": "fly", "status": "active", "created_at": "2024-01-15T12:00:00Z", "expires_at": "2024-04-15T12:00:00Z", "issuer": null, "issued": [ { "type": "ecdsa", "expires_at": "2024-04-15T12:00:00Z", "certificate_authority": "lets_encrypt" } ] } ], "validation": { "dns_configured": true, "alpn_configured": true, "http_configured": true, "ownership_txt_configured": true }, "dns_requirements": { "a": ["137.66.XXX.XXX"], "aaaa": ["2a09:8280:1::XXXX"], "cname": "my-app-name.fly.dev", "acme_challenge": { "name": "_acme-challenge.example.com", "target": "example.com.XXXXX.flydns.net" }, "ownership": { "name": "_fly-ownership.example.com", "app_value": "app-XXXXXXXXXX", "org_value": "org-XXXXXXXXXX" } }, "validation_errors": [], "dns_records": { "a": ["137.66.XXX.XXX"], "aaaa": ["2a09:8280:1::XXXX"], "cname": [], "resolved_addresses": ["137.66.XXX.XXX", "2a09:8280:1::XXXX"], "soa": "ns1.example.com", "acme_challenge_cname": "example.com.XXXXX.flydns.net", "ownership_txt": "app-XXXXXXXXXX" } } ``` ## Delete hostname and all certificates `DELETE /apps/{app_name}/certificates/{hostname}` Remove a hostname and all associated certificates (both ACME and custom) from the app. #### Path parameters **app\_name** `string` *required* The name of the Fly App. **hostname** `string` *required* The hostname to remove. ```sh title="DELETE/v1/apps/{app_name}/certificates/{hostname}" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com" ``` ```txt title="Status: 204 no content" theme={null} no body ``` ## Delete ACME certificates `DELETE /apps/{app_name}/certificates/{hostname}/acme` Stop ACME certificate issuance for a hostname. If a custom certificate exists, it will continue to be served. The hostname itself is not removed. #### Path parameters **app\_name** `string` *required* The name of the Fly App. **hostname** `string` *required* The hostname to stop ACME issuance for. ```sh title="DELETE/v1/apps/{app_name}/certificates/{hostname}/acme" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com/acme" ``` ```json title="Status: 200 OK - Example response" theme={null} { "hostname": "example.com", "configured": true, "acme_requested": false, "status": "active", "certificates": [ { "source": "custom", "status": "active", "created_at": "2024-01-15T10:30:00Z", "expires_at": "2039-01-15T10:30:00Z", "issuer": "Cloudflare Origin Certificate", "issued": [] } ], "validation": { "dns_configured": true, "alpn_configured": false, "http_configured": false, "ownership_txt_configured": true }, "dns_requirements": { "..." : "..." }, "validation_errors": [] } ``` ## Delete custom certificate `DELETE /apps/{app_name}/certificates/{hostname}/custom` Remove the custom certificate for a hostname. If ACME certificates are configured, they will continue to be served. The hostname itself is not removed. #### Path parameters **app\_name** `string` *required* The name of the Fly App. **hostname** `string` *required* The hostname to remove the custom certificate from. ```sh title="DELETE/v1/apps/{app_name}/certificates/{hostname}/custom" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/certificates/example.com/custom" ``` ```json title="Status: 200 OK - Example response" theme={null} { "hostname": "example.com", "configured": true, "acme_requested": true, "status": "active", "certificates": [ { "source": "fly", "status": "active", "created_at": "2024-01-15T12:00:00Z", "expires_at": "2024-04-15T12:00:00Z", "issuer": null, "issued": [ { "type": "ecdsa", "expires_at": "2024-04-15T12:00:00Z", "certificate_authority": "lets_encrypt" } ] } ], "validation": { "dns_configured": true, "alpn_configured": true, "http_configured": true, "ownership_txt_configured": true }, "dns_requirements": { "..." : "..." }, "validation_errors": [] } ``` ## Related topics * [Custom domains](/networking/custom-domain/) * [Using Cloudflare with Fly.io](/networking/understanding-cloudflare/) * [Working with the Machines API](/machines/api/working-with-machines-api/) * [Apps resource](/machines/api/apps-resource/) reference * [Machines resource](/machines/api/machines-resource/) reference * [OpenAPI specification](/api/machines) # Machines API Source: https://docs.fly.io/machines/api/index Manage apps, Machines, volumes, certificates and tokens with the Fly.io Machines REST API. Find each resource reference, the OpenAPI spec and the fly-go client. ![Illustration by Annie Ruygt of a group of hovering servers with eyes](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/machine-api.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=90fcb9a7581731490c9eac1b2848758d) The Fly Machines REST API provides resources to provision and manage Fly Apps, Fly Machines, and Fly Volumes. * **[Working with the Machines API](/machines/api/working-with-machines-api):** Get set up quickly to use the Machines API. * **[Machines resource](/machines/api/machines-resource):** The core of the Machines API. Use the Machines resource to fully control Machines at speed. * **[Apps resource](/machines/api/apps-resource):** Create and manage Fly Apps to group and administer your Machines. * **[Certificates resource](/machines/api/certificates-resource):** Manage SSL/TLS certificates for custom domains. * **[Tokens resource](/machines/api/tokens-resource):** Request an OpenID Connect token from a 3rd-party. * **[Volumes resource](/machines/api/volumes-resource):** Create and manage persistent storage volumes for your Machines. * **[OpenAPI spec](/api/machines):** OpenAPI 3.0 specification for the Machines API. ## Client libraries You can call the Machines API from any HTTP client. The official packages from Fly.io: * **[fly-go](https://pkg.go.dev/github.com/superfly/fly-go):** Go client for the Machines API and the Fly.io GraphQL API. It's the library [flyctl](https://github.com/superfly/flyctl) is built on. Module `github.com/superfly/fly-go`. * **Other languages:** generate a client from the [OpenAPI spec](https://docs.fly.io/api/machines/openapi.json) with a tool like [openapi-generator](https://openapi-generator.tech/). [Fly Sprites](/sprites) is a separate API (`api.sprites.dev`) with its own SDKs: [`@fly/sprites`](https://www.npmjs.com/package/@fly/sprites) for JavaScript and TypeScript, [`sprites-py`](https://pypi.org/project/sprites-py/) for Python, [`sprites-go`](https://pkg.go.dev/github.com/superfly/sprites-go) for Go, [`sprites`](https://hex.pm/packages/sprites) for Elixir, and an [MCP server](/sprites/integrations/remote-mcp). # Machines Source: https://docs.fly.io/machines/api/machines-resource Create, list, update, stop, suspend, start, and delete Machines on Fly.io with the Machines API, plus leases, metadata, wait states, and config properties. You can use the Machines resource to create, stop, start, update, and delete Fly Machines. Fly Machines are fast-launching VMs on Fly.io. The Machine resource is the configuration and state for a Machine. Endpoints [GET /v1/apps/\{app\_name}/machines](#list-machines) [POST /v1/apps/\{app\_name}/machines](#create-a-machine) [GET /v1/apps/\{app\_name}/machines/\{machine\_id}/wait](#wait-for-a-machine-to-reach-a-specified-state) [GET /v1/apps/\{app\_name}/machines/\{machine\_id}](#get-a-machine) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}](#update-a-machine) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/stop](#stop-a-machine) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/suspend](#suspend-a-machine) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/start](#start-a-machine) [DELETE /v1/apps/\{app\_name}/machines/\{machine\_id}](#delete-a-machine-permanently) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/lease](#create-a-machine-lease) [GET /v1/apps/\{app\_name}/machines/\{machine\_id}/lease](#get-a-machine-lease) [DELETE /v1/apps/\{app\_name}/machines/\{machine\_id}/lease](#release-a-machine-lease) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/cordon](#route-requests-away-from-or-back-to-a-machine) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/uncordon](#route-requests-away-from-or-back-to-a-machine) [GET /v1/apps/\{app\_name}/machines/\{machine\_id}/metadata](#get-a-machines-metadata) [POST /v1/apps/\{app\_name}/machines/\{machine\_id}/metadata/\{key}](#add-or-update-machine-metadata) [DELETE /v1/apps/\{app\_name}/machines/\{machine\_id}/metadata/\{key}](#delete-machine-metadata) ## Machine properties | Property | Description | | - | - | | `id` | A stable identifier for the Machine. | | `name` | Unique name for the Machine. If omitted, one is generated for you. | | `state` | The [state](/machines/machine-states/) of the Machine. | | `region` | The region where the Machine resides, or the target region for the Machine on create. If omitted, the Machine is placed in the same region as your WireGuard peer connection (somewhere near you). | | `instance_id` | An identifier for the current running/ready version of the Machine. Every Update request potentially changes the `instance_id`. | | `private_ip` | The [6PN IPv6 address](/networking/private-networking/) of the Machine, which is where it’s reachable to other Machines in the same organization and `network_id`. | | `config` | See the [`config` object properties](#machine-config-object-properties) section. Object that defines the Machine configuration. | | `checks` | Object that provides the status of any configured health checks. | | `image_ref` | Object that defines the image details. | | `created_at` | Date and time the Machine was created. | | `updated_at` | Date and time the Machine was last updated. | | `events` | Array of objects that provide log of what’s happened with this Machine. | | `nonce` | The Machines lease nonce, if this Machine is currently leased. Also returned on Machine create if `lease_ttl` is provided. | ## List Machines `GET /v1/apps/{app_name}/machines` List all the Machines for an app. #### Path parameters **app\_name** `string` *required* The name of the Fly App to list Machines for. #### Query parameters **include\_deleted** `Boolean` If true, include deleted Machines in the response. **region** `String` Filter by region. For example, to return only Machines in the `yyz` region: `GET /v1/apps/my-app-name/machines?region=yyz` **metadata.\{key}** `String` Filter by metadata key-value pair. For example, to return only Machines with metadata item `"foo": "bar"` : `GET /v1/apps/my-app-name/machines?metadata.foo=bar` ```sh title="GET/v1/apps/{app_name}/machines" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" ``` ```json title="Status: 200 OK - Example response" theme={null} [ { "id": "a5c5de9ce64ca12", "name": "aged-wind-2649", "state": "started", "region": "ord", "image_ref": { "registry": "registry-1.docker.io", "repository": "rebelthor/sleep", "tag": "latest", "digest": "sha256:597c3e12f830132be2aa69b4c0deccb0657ea4253e6d59c6f38e41e9f69a0add" }, "instance_id": "1RREBN3T5K95DK9IVP4XHTTPEY2", "private_ip": "fdaa:0:18:a7b:196:e274:9ce1:2", "created_at": "2023-10-31T02:30:10Z", "updated_at": "2023-10-31T02:35:26Z", "config": { /* Fly Machine Config */ }, "events": [ { "type": "start", "status": "started", "source": "flyd", "timestamp": 1698719726615 }, { "type": "launch", "status": "created", "source": "user", "timestamp": 1698719723203 } ] }, { "id": "9c487adb2596113", "name": "summer-sun-916", "state": "started", "region": "ord", "image_ref": { "registry": "registry.fly.io", "repository": "teamcoco", "digest": "sha256:5615ef423504d56ae345ccff9db54796d589965da953297f5785042df0628452" }, ... } ] ``` ## Create a Machine `POST /v1/apps/{app_name}/machines` Given the name of a Fly App, create a Fly Machine, given the URI of a container image, in some region (or, by default, the region closest to you) on Fly.io’s platform. If successful, that Machine will boot up by default. Create a Machine without booting it by setting `skip_launch`. You can configure the Machine characteristics, like its CPU and memory. You can also allow connections from the internet through the Fly Proxy by [creating a Machine with services](#create-a-machine-with-services). Learn more about this behavior in the [networking section](#notes-on-networking). > [!NOTE] > > **Important:** This request can fail, and you’re responsible for handling that failure. If you ask for a large Machine, or a Machine in a region we happen to be at capacity for, you might need to retry the request, or to fall back to another region. If you’re working directly with the Machines API, you’re taking some responsibility for your own orchestration! The only required parameter in the body is `image` in the `config` object. #### Path parameters **app\_name** `string` *required* The name of the Fly App to create a Machine for. #### Body parameters **name** `string` Unique name for this Machine. If omitted, one is generated for you. String. **region** `string` The target region. Omitting this param launches in the same region as your WireGuard peer connection (somewhere near you). String. **lease\_ttl** `integer` Acquire a lease on the newly created Machine, waiting this many seconds before failing the request; use to create a Machine that can’t be updated by any other external process while waiting for it to come up and pass health checks. **skip\_launch** `boolean` Create a Fly Machine, but don’t boot it up, leaving it in a state where it can be quickly started in response to events. Think of this as “warming the caches” on our hardware. (default: false) **lsvd** `boolean` Enable Log Structured Virtual Disks for this Machine. (default: false) **skip\_service\_registration** `boolean` Leave this Machine disconnected from Fly.io’s request routing. This is like a combined Create and Cordon operation; register the Machine later with an Uncordon request. Useful for bluegreen deploys: bring a Machine up, test it healthy, and only then let user requests hit it. (default: false) **config** `object` *required* Required for `image`. An object defining the Machine configuration. See the [`config` object properties](#machine-config-object-properties) section. **properties** **image** `string` *required* The container registry path to the image that defines this Machine (for example, “registry-1.docker.io/library/ubuntu:latest”). ```sh title="POST/v1/apps/{app_name}/machines" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" \\ -d '{ "config": { "init": { "exec": [ "/bin/sleep", "inf" ] }, "image": "registry-1.docker.io/library/ubuntu:latest", "auto_destroy": true, "restart": { "policy": "always" }, "guest": { "cpu_kind": "shared", "cpus": 1, "memory_mb": 256 } } }' ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "1857156b526dd8", "name": "aged-thunder-7371", "state": "created", "region": "ord", "image_ref": { "registry": "registry-1.docker.io", "repository": "library/ubuntu", "tag": "latest", "digest": "sha256:c9cf959fd83770dfdefd8fb42cfef0761432af36a764c077aed54bbc5bb25368", "labels": { "org.opencontainers.image.ref.name": "ubuntu", "org.opencontainers.image.version": "22.04" } }, "instance_id": "01HEPA330HZ37A78TK080NAK1M", "private_ip": "fdaa:ff:ff:a7b:195:e229:8038:2", "created_at": "2023-11-08T01:52:30Z", "updated_at": "2023-11-08T01:52:30Z", "config": { "init": { "exec": [ "/bin/sleep", "inf" ] }, "image": "registry-1.docker.io/library/ubuntu:latest", "auto_destroy": true, "restart": { "policy": "always" }, "guest": { "cpu_kind": "shared", "cpus": 1, "memory_mb": 256 }, "dns": {} }, "events": [ { "type": "launch", "status": "created", "source": "user", "timestamp": 1699408350300 } ] } ``` ## Create a Machine with services `POST /v1/apps/{app_name}/machines` Create a Machine with services defined on app. Learn more about [services and networking](#notes-on-networking). #### Path parameters **app\_name** `string` *required* The name of the Fly App to create a Machine for. #### Body parameters **name** `string` Unique name for this Machine. If omitted, one is generated for you. String. **region** `string` The target region. Omitting this param launches in the same region as your WireGuard peer connection (somewhere near you). String. **lease\_ttl** `integer` Acquire a lease on the newly created Machine, waiting this many seconds before failing the request; use to create a Machine that can’t be updated by any other external process while waiting for it to come up and pass health checks. **skip\_launch** `boolean` Create a Fly Machine, but don’t boot it up, leaving it in a state where it can be quickly started in response to events. Think of this as “warming the caches” on our hardware. (default: false) **lsvd** `boolean` Enable Log Structured Virtual Disks for this Machine. (default: false) **skip\_service\_registration** `boolean` Leave this Machine disconnected from Fly.io’s request routing. This is like a combined Create and Cordon operation; register the Machine later with an Uncordon request. Useful for bluegreen deploys: bring a Machine up, test it healthy, and only then let user requests hit it. (default: false) **config** `object` *required* Required for `image`. An object defining the Machine configuration. See the [`config` object properties](#machine-config-object-properties) section. **properties** **image** `string` *required* The container registry path to the image that defines this Machine (for example, “registry-1.docker.io/library/ubuntu:latest”). **services** `array` Defines how Fly Proxy connects requests to an app’s public Anycast or private Flycast address to services running within Machines, and configures other Fly Proxy behavior for a service. ```sh title="POST/v1/apps/{app_name}/machines" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" \\ -d '{ "name": "my-app-name", "config": { "image": "flyio/fastify-functions", "env": { "APP_ENV": "production" "services": [ { "ports": [ { "port": 443, "handlers": [ "tls", "http" ] }, { "port": 80, "handlers": [ "http" ] } ], "protocol": "tcp", "internal_port": 8080 } ], "checks": { "httpget": { "type": "http", "port": 8080, "method": "GET", "path": "/", "interval": "15s", "timeout": "10s" } } } } }' ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "1857156b526dd8", "name": "aged-thunder-7371", "state": "created", "region": "ord", "image_ref": { "registry": "registry-1.docker.io", "repository": "library/ubuntu", "tag": "latest", "digest": "sha256:c9cf959fd83770dfdefd8fb42cfef0761432af36a764c077aed54bbc5bb25368", "labels": { "org.opencontainers.image.ref.name": "ubuntu", "org.opencontainers.image.version": "22.04" } }, "instance_id": "01HEPA330HZ37A78TK080NAK1M", "private_ip": "fdaa:ff:ff:a7b:195:e229:8038:2", "created_at": "2023-11-08T01:52:30Z", "updated_at": "2023-11-08T01:52:30Z", "config": { "init": { "exec": [ "/bin/sleep", "inf" ] }, "image": "registry-1.docker.io/library/ubuntu:latest", "auto_destroy": true, "restart": { "policy": "always" }, "guest": { "cpu_kind": "shared", "cpus": 1, "memory_mb": 256 }, "dns": {} }, "events": [ { "type": "launch", "status": "created", "source": "user", "timestamp": 1699408350300 } ] } ``` ## Wait for a Machine to reach a specified state `GET /v1/apps/{app_name}/machines/{machine_id}/wait` #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to wait for. #### Query parameters **instance\_id** `string` Filter for a specific Machine `instance_id` (version). Required when waiting for Machine to be in `stopped` state. **timeout** `integer` The time, in seconds, to wait for the Machine to enter the specified state. Default is 60. **state** `string enum` The Machine state to wait for. Values are: `started`, `stopped`, `suspended`, or `destroyed`. Default is `started`. ```sh title="GET/v1/apps/{app_name}/machines/{machine_id}/wait" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589/wait?state=stopped&instance_id=01HMVXC16ED6SHK9452AF029Y7" ``` ```json title="Status: 200 OK - Example response" theme={null} { "ok": true } ``` ## Get a Machine `GET /v1/apps/{app_name}/machines/{machine_id}` Given the name of a Fly App and a Fly Machine ID, retrieve the details of that Machine. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to get. ```sh title="GET/v1/apps/{app_name}/machines/{machine_id}" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589" ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "a5c5de9ce64ca12", "name": "aged-wind-2649", "state": "started", "region": "ord", "image_ref": { "registry": "registry-1.docker.io", "repository": "rebelthor/sleep", "tag": "latest", "digest": "sha256:597c3e12f830132be2aa69b4c0deccb0657ea4253e6d59c6f38e41e9f69a0add" }, "instance_id": "1RREBN3T5K95DK9IVP4XHTTPEY2", "private_ip": "fdaa:0:18:a7b:196:e274:9ce1:2", "created_at": "2023-10-31T02:30:10Z", "updated_at": "2023-10-31T02:35:26Z", "config": { /* Fly Machine Config */ }, "events": [ { "type": "start", "status": "started", "source": "flyd", "timestamp": 1698719726615 }, { "type": "launch", "status": "created", "source": "user", "timestamp": 1698719723203 } ] } ``` ## Update a Machine `POST /v1/apps/{app_name}/machines/{machine_id}` Given the name of a Fly App and a Fly Machine ID, update the configuration of the Machine. If the Machine is running and the request is successful, it will reboot; if the Machine isn’t running, and you don’t want it to start up, set `skip_launch`. This is, in particular, how you would update the running image of a Machine (when you need to deploy new code), or roll back to a previous Machine release. It’s also how you’d vertically scale an application. > [!NOTE] > > **Important:** This request can fail, and you’re responsible for handling that failure. If you ask for a large Machine, or a Machine in a region we happen to be at capacity for, you might need to retry the request, or to fall back to another region. If you’re working directly with the Machines API, you’re taking some responsibility for your own orchestration! `region` and `name` are immutable and cannot be updated. > [!NOTE] > > **Note:** You need to specify the entire Machine config to update a Machine; we don’t support partial updates. You can get the Machine you want to update, copy and modify the `config` and include it in the body of the update request. Refer to the [Machine `config` object properties](#machine-config-object-properties) section for property descriptions. #### Request headers **fly-machine-lease-nonce** `string` The Machine lease nonce, a random value we provide that indicates that you currently hold the lease on this Machine. If the Machine is leased, and you don’t provide this header, the request to update the Machine will fail. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to update. #### Body parameters **config** `object` *required* An object defining the Machine configuration. See the [`config` object properties](#machine-config-object-properties) section. **current\_version** `string` The latest `instance_id` value of the Machine. **name** `string` Unique name for this Machine. If omitted, one is generated for you. String. **region** `string` The target region. Omitting this param launches in the same region as your WireGuard peer connection (somewhere near you). String. **lease\_ttl** `integer` Acquire a lease on the newly created Machine, waiting this many seconds before failing the request; use to create a Machine that can’t be updated by any other external process while waiting for it to come up and pass health checks. **skip\_launch** `boolean` Create a Fly Machine, but don’t boot it up, leaving it in a state where it can be quickly started in response to events. Think of this as “warming the caches” on our hardware. (default: false) **lsvd** `boolean` Enable Log Structured Virtual Disks for this Machine. (default: false) **skip\_service\_registration** `boolean` Leave this Machine disconnected from Fly.io’s request routing. This is like a combined Create and Cordon operation; register the Machine later with an Uncordon request. Useful for bluegreen deploys: bring a Machine up, test it healthy, and only then let user requests hit it. (default: false) ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589" \\ -d '{ "config": { "image": "flyio/fastify-functions", "guest": { "memory_mb": 512, "cpus": 2, "cpu_kind": "shared" }, "env": { "APP_ENV": "production" }, "services": [ { "ports": [ { "port": 443, "handlers": [ "tls", "http" ] }, { "port": 80, "handlers": [ "http" ] } ], "protocol": "tcp", "internal_port": 8080 } ] } }' ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "73d8d46dbee589", "name": "lingering-moon-48", "state": "starting", "region": "cdg", "instance_id": "01G3SHPYE8XZ58GD4XRRF9CCKC", "private_ip": "fdaa:0:3ec2:a7b:5adc:6068:5b85:2", "config": { "env": null, "init": { "exec": null, "entrypoint": null, "cmd": null, "tty": false }, "image": "flyio/fastify-functions", "metadata": null, "restart": { "policy": "" }, "guest": { "cpu_kind": "", "cpus": 2, "memory_mb": 512 } }, "image_ref": { "registry": "registry-1.docker.io", "repository": "flyio/fastify-functions", "tag": "latest", "digest": "sha256:e15c11a07e1abbc50e252ac392a908140b199190ab08963b3b5dffc2e813d1e8", "labels": { } }, "created_at": "2022-05-23T22:48:21Z" } ``` ## Stop a Machine `POST /v1/apps/{app_name}/machines/{machine_id}/stop` Stopping a started Machine will shut down the Machine, but not destroy it. The Machine can be started again with `machines//start`. Stopping a suspended Machine will invalidate its snapshot, forcing it to perform a cold boot the next time it is started. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to stop. #### Body parameters **signal** `object` Signal to stop the Machine with. (default: SIGINT) **timeout** `string` Seconds to wait before sending SIGKILL to the Machine ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/stop" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589/stop" ``` ```json title="Status: 200 OK - Example response" theme={null} { "ok": true } ``` ## Suspend a Machine `POST /v1/apps/{app_name}/machines/{machine_id}/suspend` Suspending a Machine pauses the Machine and takes a snapshot of its state, including its memory. The next start operation will attempt (but is not guaranteed) to resume the Machine from the snapshot, rather than performing a cold boot. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to suspend. ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/suspend" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589/suspend" ``` ```json title="Status: 200 OK - Example response" theme={null} { "ok": true } ``` ## Start a Machine `POST /v1/apps/{app_name}/machines/{machine_id}/start` Start a Machine. Stopped Machines that are restarted are completely reset to their original state so that they start clean on the next run. Suspended Machines that are started attempt to resume from the snapshot taken when they were suspended. If this is not possible, then they will perform a cold boot, as though starting from the stopped state; however, their root file systems will not be reset. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to start. ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/start" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589/start" ``` ```json title="Status: 200 OK - Example response" theme={null} { "previous_state": "stopped", "migrated": false, "new_host": "" } ``` ## Delete a Machine permanently `DELETE /v1/apps/{app_name}/machines/{machine_id}` Delete a Machine. This action cannot be undone. Given the name of a Fly App and the Machine ID of a Fly Machine, delete the Machine. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to delete. #### Query parameters **force** `boolean` Force stop the Machine if running. ```sh title="DELETE/v1/apps/{app_name}/machines/{machine_id}" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/24d896dec64879?force=true" ``` ```json title="Status: 200 OK - Example response" theme={null} { "ok": true } ``` ## Create a Machine lease `POST /v1/apps/{app_name}/machines/{machine_id}/lease` Create a lease for a specific Machine within an app using the details provided in the request body. Machine leases can be used to obtain an exclusive lock on modifying a Machine. The Machine lease nonce is a random value we provide that indicates that you currently hold the lease on a Machine. To use the provided nonce from the create or get lease response, add the `fly-machine-lease-nonce` header to all subsequent API calls to the Machine for the duration of the lease. See [Release a Machine lease](#release-a-machine-lease). #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to create a lease for. #### Body parameters **description** `string` A description of the lease for convenience. **ttl** `integer` The time in seconds that the lease should be held for. ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/lease" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/3d8d413b29d089/lease" \\ -d '{ "description": "", "ttl": 500 }' ``` ```json title="Status: 201 created - Example response" theme={null} { "status": "success", "data": { "nonce": "5c35f65c9f95", "expires_at": 1708569778, "owner": "hello@fly.io", "description": "", "version": "01HQ73A7BFFDF1B6WMGHFZZ4E7" } } ``` ## Get a Machine lease `GET /v1/apps/{app_name}/machines/{machine_id}/lease` Retrieve the current lease of a specific Machine within an app. Machine leases can be used to obtain an exclusive lock on modifying a Machine. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to retrieve a lease for. ```sh title="GET/v1/apps/{app_name}/machines/{machine_id}/lease" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/3d8d413b29d089/lease" ``` ```json title="Status: 200 OK - Example response" theme={null} { "status": "success", "data": { "nonce": "5c35f65c9f95", "expires_at": 1708569778, "owner": "hello@fly.io", "description": "", "version": "01HQ73A7BFFDF1B6WMGHFZZ4E7" } } ``` ## Release a Machine lease `DELETE /v1/apps/{app_name}/machines/{machine_id}/lease` Release the lease of a specific Machine within an app. Machine leases can be used to obtain an exclusive lock on modifying a Machine. #### Request headers **fly-machine-lease-nonce** `string` Required to release a lease. The Machine lease nonce, a random value provided in the response when you get or create a lease. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to release a lease for. ```sh title="DELETE/v1/apps/{app_name}/machines/{machine_id}/lease" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" -H "fly-machine-lease-nonce: fed368b018e9" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/3d8d413b29d089/lease" ``` ```json title="Status: 200 OK - Example response" theme={null} { "status": "success", "data": { "ok": true } } ``` ## Route requests away from or back to a Machine `POST /v1/apps/{app_name}/machines/{machine_id}/cordon` `POST /v1/apps/{app_name}/machines/{machine_id}/uncordon` Given the name of a Fly App and the Machine ID of a Fly Machine, instruct the Fly Proxy not to send requests to a Machine or to start sending requests again to a previously cordoned Machine. You can also do this with a fresh Machine, all in one shot, using the `skip_service_registration` request field of a Machine Create request. This is useful for bluegreen deployments: boot up a new, “green”, cordoned Machine running the new release (using `skip_service_registration`), make sure it’s healthy, then uncordon it and tear down the old, “blue” Machine. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to cordon or uncordon. ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/cordon" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589/cordon" ``` ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/uncordon" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589/uncordon" ``` ```json title="Status: 200 OK - Example response" theme={null} { "ok": true } ``` ## Get a Machine’s metadata `GET /v1/apps/{app_name}/machines/{machine_id}/metadata` Get the metadata defined in a specific Machine’s config. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to get the metadata for. ```sh title="GET/v1/apps/{app_name}/machines/{machine_id}/metadata" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/9080e339b57798/metadata" ``` ```json title="Status: 200 OK - Example response" theme={null} { "fly_flyctl_version":"0.2.8", "fly_platform_version":"v2", "fly_process_group":"app", "fly_release_id":"Nj7oGMlVVpZ7JToybJoXLLjMl", "fly_release_version":"63" } ``` ## Add or update Machine metadata `POST /v1/apps/{app_name}/machines/{machine_id}/metadata/{key}` Add or update a metadata key-value pair on a specific Machine’s config. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to update the metadata for. **key** `string` *required* A key for the metadata key-value pair to add or replace. Provide the value in the request body. #### Body parameters **value** `string` *required* The value to assign to the metadata `key` that was passed as a path parameter. ```sh title="POST/v1/apps/{app_name}/machines/{machine_id}/metadata/{key}" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/9080e339b57798/metadata/foo-key" \\ -d '{ "value":"bar" }' ``` ```txt title="Status: 204 No content" theme={null} no body ``` ## Delete Machine metadata `DELETE /v1/apps/{app_name}/machines/{machine_id}/metadata/{key}` Delete a metadata key-value pair on a specific Machine’s config. #### Path parameters **app\_name** `string` *required* The name of the Fly App the Machine belongs to. **machine\_id** `string` *required* The ID of the Machine to delete some metadata for. **key** `string` *required* The key of the metadata to delete. ```sh title="DELETE/v1/apps/{app_name}/machines/{machine_id}/metadata/{key}" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/9080e339b57798/metadata/foo-key" ``` ```txt title="Status: 204 No content" theme={null} no body ``` ## Notes on networking Machines are closed to the public internet by default. To make them accessible via the associated application, you need to: * Allocate an IP address to the Fly App * Add one or more `services` to the Machine config with ports and handlers, as shown in [Create a Machine with services](#create-a-machine-with-services). For an application with a single Machine, all requests will be routed to that Machine. A Machine in the `stopped` state will be started up automatically when a request arrives. For an application with multiple Machines *with the same configuration*, requests will be distributed across them. Warm-start behavior in this situation is not well-defined now, so should not be relied upon for apps with multiple Machines. Requests to Machines with *mixed* configurations will be distributed across Machines whose configurations match the request. For example, if 3 out of 6 Machines have service configurations set to listen on port 80, requests to port 80 will be distributed amongst those 3. ### Reaching Machines Machines can be reached within the private network by hostname, in the format `.vm..internal`. For example, to reach a Machine with ID `3d8d413b29d089` on an app called `my-app-name`, use hostname `3d8d413b29d089.vm.my-app-name.internal`. ## Machine `config` object properties Properties of the `config` object for Machine configuration. See [Machine properties](#machine-properties). **`image`:** string - Required. The container registry path to the image that defines this Machine (for example, “registry-1.docker.io/library/ubuntu:latest”). *** **`auto_destroy`:** bool (false) - If true, the Machine destroys itself once it’s complete. *** **`checks`:** An optional object that defines one or more named checks. The key for each check is the check name. The value for each check supports: * `type`: string (nil) - `tcp` or `http`. * `port`: int (nil) - The TCP port to connect to, likely should be the same as `internal_port`. * `interval`: int (nil) - The interval, in nanoseconds, between connectivity checks * `timeout`: int (nil) - The maximum time, in nanoseconds, a connection can take before being reported as failing its health check. * `grace_period`: int (nil) - How long to wait, in nanoseconds, before we start running health checks. * `method`: string (nil) - For `http` checks, the HTTP method to use to when making the request. * `path`: string (nil) - For `http` checks, the path to send the request to. * `protocol`: string (nil) - For `http` checks, whether to use `http` or `https` * `tls_server_name`: string (nil) - If the protocol is `https`, the hostname to use for TLS certificate validation * `tls_skip_verify`: bool (false) - For `http` checks with https protocol, whether or not to verify the TLS certificate * `headers`: \{string: \[string, string]} () - For `http` checks, an array of objects with string field `name` and array of strings field `values`. An example of two checks: ``` "checks": { "tcp-alive": { "type": "tcp", "port": 8080, "interval": "15s", "timeout": "10s" }, "http-get": { "type": "http", "port": 8080, "protocol": "http" "method": "GET", "path": "/", "interval": "15s", "timeout": "10s" } } ``` *** **`dns`:** * `nameservers`: Used for Fly Kubernetes. * `searches`: Used for Fly Kubernetes. * `options`: Used for Fly Kubernetes. * `dns_forward_rules`: Used for dedicated hosts. * `skip_registration`: boolean - If true, do not register the Machine’s 6PN IP with the internal DNS system. *** **`env`:** \{string:string} () - An object filled with key/value pairs to be set as environment variables. *** **`files`:** An optional array of objects defining files to be written within a Machine, one of `raw_value` or `secret_name` must be provided. * `guest_path`: string - The path in the Machine where the file will be written. Must be an absolute path. * `raw_value`: string - Contains the base64 encoded string of the file contents. * `secret_name`: string - The name of the secret containing the base64 encoded file contents. An example of two files: ``` "files": [ { "guest_path": "/path/to/hello.txt", "raw_value": "aGVsbG8gd29ybGQK" }, { "guest_path": "/path/to/secret.txt", "secret_name": "SUPER_SECRET" } ] ``` *** **`guest`:** Configure the resources allocated for this Machine. An object with the following options: * `cpu_kind`: string (nil) - The type of CPU reservation to make (“shared”, “performance”, and so on). * `gpu_kind`: string (nil) - The type of GPU reservation to make. * `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`) * `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](/reference/configuration/#persist_rootfs) for details. *** **`init`:** Arguments for `init`, which is Fly.io’s footprint inside your Machine, and controls how your own code gets run. * `exec`: \[string, string]\(\[]) - The command line for the program to run once the Machine boots up. This overrides any other startup command line, either in our API or in your Docker container definition. * `entrypoint`: \[string, string]\(\[]) - A command line to override the ENTRYPOINT of your Docker container; another way to define the program that is going to start up when your Machine boots up. * `cmd`: \[string, string]\(\[]) - A command line to override the CMD of your Docker container; still another way to define the program that is going to start up when your Machine boots up. * `kernel_args`: Optional array of strings. Arguments passed to the kernel. * `tty`: bool (false) - Allocate a TTY for the process we start up. * `swap_size_mb`: int (nil) - Swap space to reserve for the Fly Machine in, you guessed it, megabytes. *** **`metadata`:** \{string:string} () - An object filled with key/value pairs for the Machine metadata. We use metadata internally for routing, process groups, and clusters. *** **`metrics`:** An optional object defining a metrics endpoint that [Prometheus on Fly.io](/monitoring/metrics#prometheus-on-fly-io) will scrape. * `port`: int - Required. The port that Prometheus will connect to. * `path`: string - Required. The path that Prometheus will scrape (e.g. `/metrics`). *** **`mounts`:** An array of objects that reference previously created [persistent volumes](/volumes/). Currently, you may only mount one volume per Machine. * `volume`: string - Required. The volume ID, visible in `fly volumes list`. For example `vol_2n0l3vl60qpv635d`. * `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 `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. > [!NOTE] > > 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. * `entrypoint`: An array of strings. The process that will run. * `cmd`: An array of strings. The arguments passed to the entrypoint. * `env`: An object filled with key/value pairs to be set as environment variables. * `env_from` : An array of objects that define environment variables from Machine fields. Used for Fly Kubernetes. * `exec`: An array of strings. The command to run for Machines in this process group on startup. * `user`: string (nil) - An optional user that the process runs under. * `ignore_app_secrets`: boolean - If true, only use the secrets provided at the process level. Default false. * `secrets`: An array of strings. Set the secrets in the environment of the Machine. `env_var` is required and is the name of the environment variable that will be set from the secret. It must be a valid environment variable name. `name` is optional and when provided is used to reference a secret name where the `env_var` is different from what was set as the secret name. *** **`restart`:** Defines whether and how flyd restarts a Machine after its main process exits. Learn more about [Machine restart policies](/machines/guides-examples/machine-restart-policy/). This object has the following options: * `policy`: string - Required. One of “no”, “on-failure”, or “always”. * `max_retries`: int (nil) - The maximum number of retries when the policy is “on-failure”. *** **`schedule`:** string (nil) - Optionally one of `hourly`, `daily`, `weekly`, `monthly`. Runs Machine at the given interval. Interval starts at time of Machine creation. *** **`services`**: An array of objects that define a single network service. Check the [Machines networking section](#notes-on-networking) for more information. * `protocol`: string - Required. `tcp` or `udp`. [Learn more about running raw TCP/UDP services](/networking/udp-and-tcp/). * `internal_port`: int - Required. Port the Machine listens on. * `concurrency`: Control Fly Proxy’s load balancing for this service. * `type`: string - `connections` (TCP) or `requests` (HTTP). Default is `connections`. Determines which kind of event we count for load balancing. * `soft_limit`: int (nil) - Ideal service concurrency. We will attempt to spread load to keep services at or below this limit. We’ll deprioritize a Machine to give other Machines a chance to absorb traffic. Defaults to 20 when unset. * `hard_limit`: int (nil) - Maximum allowed concurrency. The limit of events at which we’ll stop routing to a Machine altogether, and, if configured to do so, potentially start up existing Machines to handle the load. Defaults to unlimited when unset. * `ports`: MachinePort - An array of objects defining the service’s ports and associated handlers. Options: * `port`: int (nil) - The internet-exposed port to receive traffic on; if you want HTTP traffic routed to 8080/tcp on your Machine, this would be 80. * `start_port`, `end-port`: int (nil) - Like `port`, but allocate a range of ports to route internally, for applications that want to occupy whole port ranges. * `handlers`: Array of protocol [handlers](/networking/services/#connection-handlers) for this port. How should the Fly Proxy handle and terminate this connection. Options include `http`, `tcp`, `tls`. * `force_https`: bool (false) - If true, force HTTP to HTTPS redirects. * `http_options`: Fiddly HTTP options (if you don’t know you need them, you don’t), including: * `compress`: bool (false) - If true, enable HTTP compression. * `h2_backend`: bool (false) - If true, inform Fly Proxy that your app supports HTTP/2 (h2c with prior knowledge), which enables HTTP/2 only workloads to work with the `http` handler. * `response`: Options for controlling HTTP response headers. * `headers`: (\{“headers”: \{string:string}} (nil)) HTTP headers to set on responses. * `pristine`: bool (false) - If true, do not add any Fly.io headers to HTTP responses. The following response headers won’t be added and won’t be modified if returned by the app: `Server`, `Via`, `Fly-Request-Id`, `Fly-Cache-Status`. * `tls_options`: Fiddly TLS options (if you don’t know you need to mess with these, you don’t need to), including: * `alpn`: \[string, string]\(\[]) : ALPN protocols to present TLS clients (for example, \[“mqtt”]). * `default_self_signed`: bool (false) - If true, serve a self-signed certificate if no certificate exists. * `versions`: \[string, string]\(\[]) : TLS versions to allow (for instance, \[“TLSv1.2”, “TLSv1.3”]). * `proxy_proto_options`: Configure the version of the PROXY protocol that your app accepts. Version 1 is the default. * `version`: A string to indicate that the TCP connection uses PROXY protocol version 2. The default when not set is version 1. * `autostart`: bool (false) - If true, Fly Proxy starts Machines when requests for this service arrive. * `autostop`: string or bool (`off`) - One of `off` (or false), `stop` (or true), `suspend`. If `stop`, Fly Proxy stops Machines when this service goes idle. If `suspend`, Fly Proxy instead suspends Machines if possible and stops them if not. * `min_machines_running`: int (nil) - When `autostart` is true, the minimum number of Machines to keep running at all times in the primary region. *** **`size`:** A [named size](/about/pricing/#started-fly-machines) for the VM, e.g. `performance-2x` or `shared-cpu-2x`. Note: `guest` and `size` are mutually exclusive. *** **`standbys`:** Standbys enable a Machine to be a standby for another. In the event of a hardware failure, the standby Machine will be started. Only for Machines without `services`. Array of strings representing the Machine IDs of Machines watch (act as standby for). *** **`statics`:** Optionally serve static files. * `guest_path`: string - Required. The path inside the Machines or object storage bucket where the files to serve are located. * `url_prefix`: string - Required. The URL prefix under which to serve the static files. * `tigris_bucket`: string - The Tigris bucket where the files to serve are located. * `index_document`: string - The name of the index document, served when a request is made to the root or any of its subfolders. Only works for statics hosted on Tigris *** **`stop_config`:** MachineStopConfig (nil) - Configure graceful shutdown of the Machine. * `signal`: string (nil) - The name of the signal to send to the entrypoint process on the Machine to initiate shutdown. * `timeout`: int (nil) - How long in nanoseconds to wait, after signaling the entrypoint process, before hard-shutdown of the Machine. ## Related topics * [Working with the Machines API](/machines/api/working-with-machines-api/) * [Apps resource](/machines/api/apps-resource/) reference * [Tokens resource](/machines/api/tokens-resource) reference * [Volumes resource](/machines/api/volumes-resource/) reference # Tokens Source: https://docs.fly.io/machines/api/tokens-resource Request an OpenID Connect token for a Fly Machine with POST /v1/tokens/oidc in the Machines API, setting the aud claim to get a signed JWT. You can use the Tokens resource to request an OpenID Connect (OIDC) token from a 3rd-party service. Learn more about [using OpenID Connect](/security/openid-connect/) on Fly.io. Endpoints [POST /v1/tokens/oidc](#request-an-oidc-token) ## Request an OIDC token Request an Open ID Connect token for your Machine. Customize the audience claim with the `aud` parameter. This returns a JWT token. #### Body parameters **aud** `string` The audience claim for the token (the recipient of this access token). ```sh title="POST/v1/tokens/oidc" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/tokens/oidc" \\ -d '{ "aud": "https://fly.io/my-org-slug", }' ``` ```txt title="Status: 200 OK - Example response" theme={null} ``` ## Related topics * [Working with the Machines API](/machines/api/working-with-machines-api/) * [Apps resource](/machines/api/apps-resource/) reference * [Machines resource](/machines/api/machines-resource/) reference * [Tokens resource](/machines/api/tokens-resource) reference * [Volumes resource](/machines/api/volumes-resource/) reference # Volumes Source: https://docs.fly.io/machines/api/volumes-resource Volumes endpoints in the Machines API: list, create, update, extend, and delete Fly Volumes, list snapshots, restore from one, or snapshot on demand. You can use the Volumes resource to create and delete volumes. A Fly Volume is persistent storage for a Fly Machine. Learn more about [volumes](/volumes/). Endpoints [GET /v1/apps/\{app\_name}/volumes](#list-all-the-volumes-in-an-app) [POST /v1/apps/\{app\_name}/volumes](#create-a-volume) [GET /v1/apps/\{app\_name}/volumes/\{volume\_id}](#get-a-specific-volume) [PUT /v1/apps/\{app\_name}/volumes/\{volume\_id}](#update-a-volume) [DELETE /v1/apps/\{app\_name}/volumes/\{volume\_id}](#delete-a-volume-permanently) [PUT /v1/apps/\{app\_name}/volumes/\{volume\_id}/extend](#extend-a-volume) [GET /v1/apps/\{app\_name}/volumes/\{volume\_id}/snapshots](#get-a-list-of-snapshots-for-a-volume) [POST /v1/apps/\{app\_name}/volumes/\{volume\_id}/snapshots](#create-an-on-demand-volume-snapshot-beta) ## Volume properties | 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. | | `blocks` | int | The total number of blocks in the volume. | | `blocks _avail` | int | The number of blocks available for data in the volume. | | `blocks_free` | int | The total number of blocks free for data and root user ops. | | `created_at` | string | The date and time the volume was created. | | `encrypted` | boolean | Whether the volume is encrypted. Default true. | | `fstype` | string | The file system type. | | `id` | string | The volume ID. | | `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 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. | ## List all the volumes in an app `GET /apps/{app_name}/volumes` Given the name of a Fly App, get a list of all the volumes that belong to it. #### Path parameters **app\_name** `string` *required* The name of the Fly App to list volumes for. ```sh title="GET/v1/apps/{app_name}/volumes" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes" ``` ```json title="Status: 200 OK - Example response" theme={null} [ { "id": "vol_9vw681egy1jj5xm4", "name": "disk", "state": "created", "size_gb": 3, "region": "yul", "zone": "09cd", "encrypted": true, "attached_machine_id": "908057ef21e487", "attached_alloc_id": null, "created_at": "2023-09-01T19:47:14.774Z", "blocks": 768250, "block_size": 4096, "blocks_free": 768244, "blocks_avail": 730163, "fstype": "ext4", "snapshot_retention": 5, "auto_backup_enabled": true, "host_dedication_key": "" }, { "id": "vol_q4qeekqzxze29dw4", "name": "disk", "state": "created", "size_gb": 3, "region": "iad", "zone": "a4df", "encrypted": true, "attached_machine_id": null, "attached_alloc_id": null, "created_at": "2024-01-02T21:16:38.996Z", "blocks": 751366, "block_size": 4096, "blocks_free": 751360, "blocks_avail": 708148, "fstype": "ext4", "snapshot_retention": 5, "auto_backup_enabled": true, "host_dedication_key": "" } ] ``` ## Create a volume `POST /apps/{app_name}/volumes` Create a volume for a specific app according to the configuration provided in the request body. #### Path parameters **app\_name** `string` *required* The name of the Fly App to create a volume for. ```sh title="POST/v1/apps/{app_name}/volumes" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes" \\ -d '{ "name": "my_app_vol", "region": "ord", "size_gb": 10 }' ``` ```json title="Request body schema" theme={null} { "auto_backup_enabled": true, "compute": { "cpu_kind": "string", "cpus": 0, "gpu_kind": "string", "gpus": 0, "host_dedication_id": "string", "kernel_args": ["string"], "memory_mb": 0 }, "encrypted": true, "fstype": "string", "machines_only": true, "name": "string", "region": "string", "require_unique_zone": true, "size_gb": 0, "snapshot_id": "string", "snapshot_retention": 0, "source_volume_id": "string" } ``` #### Body parameters **auto\_backup\_enabled** `bool` Enable automatic daily snapshots. Default true. **compute** `object` An optional object defining the compute specifications for the expected Machine size and type that the volume will attach to. Used to place the volume on hardware that can host that Machine. **compute properties** **compute.cpu\_kind** `string` The CPU kind, `shared` or `performance`. **compute.cpus** `int` The number of CPUs. **compute.gpu\_kind** `string` The GPU kind, for example `a100-pcie-40gb`. **compute.gpus** `int` The number of GPUs. **compute.host\_dedication\_id** `string` The ID of a dedicated host to place the volume on. **compute.kernel\_args** `string[]` Kernel arguments for the Machine. **compute.memory\_mb** `int` Memory in MB. **fstype** `string` The file system type. Default `ext4`. **machines\_only** `boolean` If true, the volume can only be attached to Machines (not Nomad allocations). **encrypted** `boolean` Whether to encrypt the volume. Default true. **name** `string` *required* The name for the new volume. **region** `string` 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` The size of the volume in GB. Default 3. **snapshot\_id** `string` The ID of the volume snapshot to use to create the new volume. **source\_volume\_id** `string` The ID of the source volume for the volume fork. **require\_unique\_zone** `boolean` 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` The number of days to retain snapshots. Defaults to 5 when not set. Min 1, max 60. ```json title="Status: 200 OK - Example response" theme={null} { "id": "vol_340088w293z35lp4", "name": "new_1028_vol", "state": "created", "size_gb": 10, "region": "ord", "zone": "84d3", "encrypted": true, "attached_machine_id": null, "attached_alloc_id": null, "created_at": "2023-11-27T21:47:06.837Z", "blocks": 0, "block_size": 0, "blocks_free": 0, "blocks_avail": 0, "fstype": "", "snapshot_retention": 5, "auto_backup_enabled": true, "host_dedication_key": "" } ``` ## Get a specific volume `GET /apps/{app_name}/volumes/{volume_id}` Retrieve details about a specific volume by its ID within an app. #### Path parameters **app\_name** `string` *required* The name of the Fly App the volume belongs to. **volume\_id** `string` *required* The ID of the volume to get. ```sh title="GET/v1/apps/{app_name}/volumes/{volume_id}" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes/vol_6r7ye90k98ynwk1r" ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "vvol_6r7ye90k98ynwk1r", "name": "disk", "state": "created", "size_gb": 3, "region": "yul", "zone": "09cd", "encrypted": true, "attached_machine_id": "6e8297dc244287", "attached_alloc_id": null, "created_at": "2023-09-01T19:47:14.774Z", "blocks": 768250, "block_size": 4096, "blocks_free": 768244, "blocks_avail": 730163, "fstype": "ext4", "snapshot_retention": 5, "auto_backup_enabled": true, "host_dedication_key": "" } ``` ## Update a volume `PUT /apps/{app_name}/volumes/{volume_id}` Update parameters on the volume. #### Path parameters **app\_name** `string` *required* The name of the Fly App to create a volume for. **volume\_id** `string` *required* The ID of the volume to get. #### Body parameters **auto\_backup\_enabled** `bool` Enable automatic daily snapshots. Default true. **snapshot\_retention** `int` The number of days to retain snapshots. Defaults to 5 when not set. Min 1, max 60. ```sh title="PUT/v1/apps/{app_name}/volumes/{volume_id}" theme={null} curl -i -X PUT \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes/vol_6r7ye90k98ynwk1r" \\ -d '{ "snapshot_retention": 10 }' ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "vol_340088w293z35lp4", "name": "new_1028_vol", "state": "created", "size_gb": 10, "region": "ord", "zone": "84d3", "encrypted": true, "attached_machine_id": null, "attached_alloc_id": null, "created_at": "2023-11-27T21:47:06.837Z", "blocks": 0, "block_size": 0, "blocks_free": 0, "blocks_avail": 0, "fstype": "", "snapshot_retention": 10, "auto_backup_enabled": true, "host_dedication_key": "" } ``` ## Delete a volume permanently `DELETE /apps/{app_name}/volumes/{volume_id}` Delete a volume. This action cannot be undone. Given the name of a Fly App and the volume ID of a Fly volume, delete the volume. #### Path parameters **app\_name** `string` *required* The name of the Fly App the volume belongs to. **volume\_id** `string` *required* The ID of the volume to permanently delete. ```sh title="DELETE/v1/apps/{app_name}/volumes/{volume_id}" theme={null} curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes/vol_6r7ye90k98ynwk1r" ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "vol_grnejj355dqdj9kr", "name": "new_1028_vol", "state": "destroyed", "size_gb": 3, "region": "yyz", "zone": "aeee", "encrypted": true, "attached_machine_id": null, "attached_alloc_id": null, "created_at": "2023-12-07T21:35:57.42Z", "blocks": 0, "block_size": 0, "blocks_free": 0, "blocks_avail": 0, "fstype": "", "snapshot_retention": 0, "auto_backup_enabled": true, "host_dedication_key": "" } ``` ## Extend a volume `PUT /apps/{app_name}/volumes/{volume_id}/extend` Given the name of a Fly App and a volume ID, you can make a volume bigger by extending it. You can extend (increase) a volume’s size, but you can’t make a volume smaller. #### Path parameters **app\_name** `string` *required* The name of the Fly App the volume belongs to. **volume\_id** `string` *required* The ID of the volume to extend. #### Body parameters **size\_gb** `integer` *required* The size in GB to make the volume. ```sh title="PUT/v1/apps/{app_name}/volumes/{volume_id}/extend" theme={null} curl -i -X PUT \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes/vol_6r7ye90k98ynwk1r/extend" \\ -d '{ "size_gb": 10, }' ``` ```json title="Status: 200 OK - Example response" theme={null} { "volume": { "id": "vol_9vw681egy1jj5xm4", "name": "disk", "state": "created", "size_gb": 10, "region": "yul", "zone": "09cd", "encrypted": true, "attached_machine_id": "6e8297dc244287", "attached_alloc_id": null, "created_at": "2023-09-01T19:47:14.774Z", "blocks": 0, "block_size": 0, "blocks_free": 0, "blocks_avail": 0, "fstype": "", "snapshot_retention": 5, "auto_backup_enabled": true, "host_dedication_key": "" }, "needs_restart": false } ``` If `needs_restart` is true, then your Machine needs to be restarted to make use of the added space. ## Get a list of snapshots for a volume `GET /apps/{app_name}/volumes/{volume_id}/snapshots` Given the name of a Fly app and a volume ID, list the available snapshots for that volume. #### Path parameters **app\_name** `string` *required* The name of the Fly App to list snapshots for. **volume\_id** `string` *required* The ID of the volume to list snapshots for. ```sh title="GET/v1/apps/{app_name}/volumes/{volume_id}/snapshots" theme={null} curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes/vol_6r7ye90k98ynwk1r/snapshots" \\ ``` ```json title="Status: 200 OK - Example response" theme={null} [ { "id": "vs_4LNvXLLK0P6tk6KgqoexaBw", "size": 36007729, "digest": "76d64a69199766d1600d46f0fd48ad9c-1", "created_at": "2023-12-02T20:59:35+00:00", "retention_days": 5 }, { "id": "vs_3NlZ9NNmpvoSPqG6DO8BkDy", "size": 36007729, "digest": "e06b15e5467de62c5d505fa57923db93-1", "created_at": "2023-12-03T21:00:35+00:00", "retention_days": 5 }, { "id": "vs_Ql8xbllZOYDSDgo2D7NYGj", "size": 36007729, "digest": "c37d2a590a351fa561fdb1ffd2f53d62-1", "created_at": "2023-12-04T21:01:35+00:00", "retention_days": 5 }, { "id": "vs_x4AxX44lmpbcpap7vwX5x8e", "size": 36007729, "digest": "91fac554e5261d8f4eb6f7b69e88c8f9-1", "created_at": "2023-12-05T21:01:55+00:00", "retention_days": 5 }, { "id": "vs_DkV2wkk7av1c9Kmlv79PL4o", "size": 36007729, "digest": "d78b5ff73a87b2bdfecb4a87c89c312b-1", "created_at": "2023-12-06T21:02:55+00:00", "retention_days": 5 }, { "id": "vs_K8z1w88ZBOys22zqKGZ36R", "size": 36007729, "digest": "95dfe85046c67834566ccff2a29b18ec-1", "created_at": "2023-12-07T21:03:25+00:00", "retention_days": 10 } ] ``` ## Create a volume from a snapshot `POST /apps/{app_name}/volumes` Create a new volume from a snapshot backup. #### Path parameters **app\_name** `string` *required* The name of the Fly App to create a volume for. #### Body parameters **name** `string` *required* The name for the new volume. **region** `string` *required* The target region. Must be in the same region as the Machine you want to attach it to. **size\_gb** `int` The size of the volume in GB. Default 3. **snapshot\_id** `string` The ID of the volume snapshot to use to create the new volume. ```sh title="POST/v1/apps/{app_name}/volumes" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes" \\ -d '{ "name": "my-app-vol", "region": "yyz", "size_gb": 3, "snapshot_id": "vs_evl65mZQ937tQ16xGGDX8BN", }' ``` ```json title="Status: 200 OK - Example response" theme={null} { "id": "vol_6vj0ggxl7zjkm2zr", "name": "new_1028_vol", "state": "restoring", "size_gb": 3, "region": "yyz", "zone": "75ec", "encrypted": true, "attached_machine_id": null, "attached_alloc_id": null, "created_at": "2024-02-23T00:41:02.411Z", "blocks": 0, "block_size": 0, "blocks_free": 0, "blocks_avail": 0, "fstype": "", "snapshot_retention": 5, "auto_backup_enabled": true, "host_dedication_key": "" } ``` If you wait a moment and then get the new volume, the state should be `“created”`. ## Create an on-demand volume snapshot (beta) `POST /apps/{app_name}/volumes/{volume_id}/snapshots` Create an on-demand volume snapshot. #### Path parameters **app\_name** `string` *required* The name of the Fly App to create a snapshot for. **volume\_id** `string` *required* The ID of the volume to create a snapshot of. ```sh title="POST/v1/apps/{app_name}/volumes/{volume_id}/snapshots" theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/volumes/vol_6r7ye90k98ynwk1r/snapshots" ``` ```json title="Status: 200 OK - Example response" theme={null} { "Msg": { "backup": { "id": "85999", "app_id": "1700318", "volume_id": "9151365284627104", "state": "prepare", "type": "BACKUP_TYPE_ON_DEMAND", "message": "", "created_at": "2024-02-16T19:30:43.914350121Z", "updated_at": "2024-02-16T19:30:43.914350121Z", "finished_at": null, "graph_id": "vs_gwMAXwwLjOVuo7jL96v2kGXU2" } } } ``` Wait a few moments for the snapshot to get created and then [get the list of snapshots](#get-a-list-of-snapshots-for-a-volume) to confirm. ## Related topics * [Working with the Machines API](/machines/api/working-with-machines-api/) * [Apps resource](/machines/api/apps-resource/) reference * [Machines resource](/machines/api/machines-resource/) reference * [Tokens resource](/machines/api/tokens-resource) reference # Working with the Machines API Source: https://docs.fly.io/machines/api/working-with-machines-api Connect to the Fly Machines API on Fly.io: choose the public or internal base URL, authenticate with a bearer token, and review response codes and rate limits. The Fly Machines API provides resources to create, destroy, and manage Fly Apps, Fly Machines, and Fly Volumes. Refer to the following reference docs for the usage of each: **[Apps resource](/machines/api/apps-resource)** - Every Fly Machine belongs to a [Fly App](/apps/overview). **[Machines resource](/machines/api/machines-resource)** - Fly Machines are our fast-launching VMs. **[Tokens resource](/machines/api/tokens-resource)** - Support for OpenID Connect tokens. **[Volumes resource](/machines/api/volumes-resource)** - [Fly Volumes](/volumes) are persistent storage for Machines. ## Connecting to the API ### API addresses There are two base URLs available to connect to the Machines API service. **Internal base URL:** `http://_api.internal:4280` **Public base URL:** `https://api.machines.dev` From within your Fly.io [private WireGuard network](/networking/private-networking), you can connect to the API directly using the internal endpoint. From outside the Fly.io WireGuard mesh, use the public endpoint; this proxies your request to the API. ### Authentication All requests must include an API token in the HTTP headers as follows: ``` Authorization: Bearer ``` Replace `` with a Fly.io authentication token. ## Environment setup The examples in the Machines API reference docs assume that you have two environment variables set: `FLY_API_TOKEN`, the authorization token to use with the API call; and `FLY_API_HOSTNAME`, the API base URL. For local development, you might set them as follows, assuming you have flyctl installed and have authenticated to Fly.io: ```bash theme={null} $ export FLY_API_HOSTNAME="https://api.machines.dev" # set to http://_api.internal:4280 when using WireGuard $ export FLY_API_TOKEN=$(fly tokens deploy) ``` The `fly tokens deploy` command creates a token capable of managing the application in your current directory. See `fly tokens create --help` for information on other types of tokens you can generate. To set an access token as an environment variable on Fly Machines, use a [secret](/apps/secrets); for example: ```bash theme={null} fly secrets set FLY_API_TOKEN="$(fly tokens deploy)" ``` ## Response Codes The Machines API uses conventional HTTP status codes to provide more information about the response. Typically, 2xx HTTP status codes denote successful operations, 4xx codes imply failures related to the user, and 5xx codes suggest problems with the infrastructure. | Status | Description | | - | - | | 200 | Successful request. | | 201 | Created successfully. | | 202 | Accepted (success). | | 204 | Successful request. No content. | | 400 | Bad request. Check that the parameters were correct. | | 401 | The API key used was missing or invalid. | | 404 | The resource was not found. | | 408 | Request timed out. | | 422 | Validation failed (for example, an app name that is already taken). | | 5xx | Indicates an error with Fly.io API servers. | ## Rate Limits 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](/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. # CPU Performance Source: https://docs.fly.io/machines/cpu-performance Compare shared and performance vCPUs on Fly.io Machines, how CPU quotas and throttling are enforced, how shared CPUs burst, and which metrics show throttling. We offer two kinds of virtual CPUs for Machines: `shared` and `performance`. Both run on the same physical hardware, have the same clock speed, etc... The difference is how much time they are allowed to spend running your applications. | CPU Type | Period1 | Baseline Quota1 | Initial Burst Balance1 | Max Burst Balance1 | | - | - | - | - | - | | `shared` | 80ms | 5ms (6.25%) | 5s | 500s | | `performance` | 80ms | 80ms (100%) | - | - | We enforce limits through the [Linux scheduler's CPU bandwidth control](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.rst) by adjusting the `cpu.cfs_quota_us` setting on each machine cgroup. For each 80ms period of time, we set a quota of 5ms for each `shared` vCPU, or the full 80ms period for each `performance` vCPU. If your machine cgroup's CPU usage reaches its quota, its tasks will be 'throttled' (not scheduled to run again) for the remainder of the 80ms period. Quotas are shared between a Machine's vCPUs. For example, a `shared-cpu-2x` Machine is allowed to run for 10ms per 80ms period, regardless of which vCPU is using that time. 1 We might change these specific numbers over time. ## Bursting APIs and human-facing web applications are sensitive to latency and a 75ms pause in program execution is often unacceptable. These same types of applications often work hard in small bursts and remain idle much of the time. To improve the performance of `shared` vCPUs for these applications, we allow a balance of unused CPU time to be accrued. The application is then allowed to spend its balance in bursts. When bursting, the vCPU is allowed to run at up to 100%. When the balance is depleted, the vCPU is limited to running at its baseline quota. Because the burst balance is only used for utilization above the baseline quota, the amount of time a machine can *actually* run at 100% will be longer than the accumulated balance. For example, a `shared` CPU with a burst balance of 500 seconds can burst for `500/(1-.0625)` = 533 seconds. This adjustment is already applied to the burst balance displayed in the dashboard panel. ## Monitoring We publish a number of [Instance Load and CPU](/monitoring/metrics#instance-load-and-cpu) platform metrics you can use to monitor quota and throttling behavior of your Machines. The easiest way to visualize these metrics is on the [CPU Quota Balance and Throttling](https://fly-metrics.net/d/fly-instance/fly-instance?viewPanel=69) panel, on the [Fly Instance](https://fly-metrics.net/d/fly-instance/fly-instance) dashboard in [Managed Grafana](/monitoring/metrics#managed-grafana): ![chart showing CPU utilization, steal, baseline, and throttling](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/cpu-quota.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=d134af7aa1f72f6769efcf2a5a6d3d3f) On the dashboard, utilization (0-100%) is displayed as a per-vCPU average, divided by the total number of vCPUs in the instance. (There is also a Per-CPU Utilization panel for a detailed breakdown of utilization across each individual vCPU, but because quotas are shared between a Machine's vCPUs only the average matters to the enforced limits.) Here, we can see a Machine whose `utilization` was running well below its `baseline` quota of 65%, and had accumulated a 50-second burst `balance`. Then, during a burst of activity, CPU utilization exceeded the baseline quota, causing the balance to drain. When the balance reached 0, the Machine was briefly `throttle`d. When CPU utilization went down, throttling ended and the balance accumulated again. A related and somewhat misleading metric is CPU steal. You can see this under the `mode=steal` label in the `fly_instance_cpu` metric. Steal is the amount of time your vCPUs are waiting to run, but the scheduler isn't allowing them to. This can happen when your Machine exceeds its quota and is throttled, but it can also be a sign that other Machines on the same host are competing for resources. In our dashboard panels, we visualize `steal` separately from other CPU utilization since it doesn't consume any CPU quota. We publish a separate `fly_instance_cpu_throttle` metric that only includes time your vCPUs were throttled for exceeding quota. # Run a new Machine Source: https://docs.fly.io/machines/flyctl/fly-machine-run Run a new Fly Machine on Fly.io with fly machine run: build or pull an image, pick a region, set resources, env vars and services, or open a temporary shell. ![Illustration by Annie Ruygt of a phoenix jumping with a motor bike](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/moto-jump.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=a7cea651006d8faa34e85012f85d767a) The [`fly machine run`](/flyctl/cmd/fly_machine_run) command is a tool to configure, build, and start a new Machine in one line. Many, but not all, [Machine configuration](/machines/api/machines-resource#machine-config-object-properties) options are available to the `fly machine run` command through flags. The available flags are listed in the flyctl help and on the [`fly machine run` reference page](/flyctl/cmd/fly_machine_run). Use `fly machine run` when you want use more than one Docker image in an app, or to run a one-off or temporary Machine. > [!NOTE] > > To create a Machine, but not start it, use [`fly machine create`](/flyctl/cmd/fly_machine_create). > > To make changes to a Machine once it's created or run, use [`fly machine update`](/flyctl/cmd/fly_machine_update). > > To create and run a new Machine with the same configuration as an existing Machine, use [`fly machine clone`](/flyctl/cmd/fly_machine_clone). > > To add a new Machine to an app managed by `fly deploy`, see [the scaling doc for Fly Launch apps](/launch/scale-count). By default, `fly machine run` creates Machines that are ignored by Fly Launch features like `fly deploy`, `fly status`, and `fly scale`. ## What it does Here's what `fly machine run` does for you: * Checks with the platform for the org and app you've specified, if any, and if needed, guides you through naming a new app. * Creates a Fly App, if applicable. * Gets, or builds, a Docker image to make the Machine from, and pushes it to the Fly.io registry if applicable. * Creates the Machine with some default config, plus config you pass to it with flags. * Starts the Machine. * By default, waits for the Machine to start, and for any configured health checks to pass, before declaring success (or failure). ## Usage Here's the usage of `fly machine run`: ```bash theme={null} fly machine run [command] [flags] ``` Here, `` can point to a prebuilt image, or to the current directory (`.`) to build from a Dockerfile. ## Administration: set the Machine's app and org The default behavior of `fly machine run` is to create a new Fly App for the new Machine to belong to, unless it's given the name of an existing app in one of two ways: 1. Like many flyctl commands, `fly machine run` will pull an app name from a `fly.toml` file if one is present in the working directory. It disregards the rest of the configuration in the file. 2. If you pass it an app name with `--app `, flyctl prefers that name over any name it gets from a `fly.toml`. If the app name doesn't belong to an existing app in one of your orgs, flyctl asks if you want to create it. It may be worth creating a `fly.toml` file with just the app name in it, to save using the `--app` option repeatedly. For example: ```toml theme={null} # a fly.toml just to provide an app name to commands # run from the same directory app = my-app-name ``` Use `--org ` to specify which organization a newly created app should belong to. The `--org` flag is ignored when creating the new Machine in an existing app. ## Name the Machine Machines have a human-friendly `name` [property](/machines/api/machines-resource#machine-properties), like `ancient-glitter-2128`, that shows up alongside the `id` in the output of the `fly machine list` command and in the web dashboard. You can give the Machine a custom name with the `--name` flag: ```bash theme={null} fly machine run . --name my-special-Machine ``` ## Choose a region Tell the Fly.io platform which [region](/reference/regions) to create the Machine in with the `--region` flag; if for some reason it can't start a new Machine in that region, you'll get an error. If the `--region` flag is omitted, the platform chooses the region that's fastest to reach from your location. This sets the `region` [property](/machines/api/machines-resource#machine-properties) of the Machine. ## Get or build the Docker image All Fly Machines are made from Docker images. Once the Machine is created, you can see this image reflected in its [`image_ref`](/machines/api/machines-resource#machine-properties) and [`config.image`](/machines/api/machines-resource#machine-config-object-properties) properties. With `fly machine run`, there are two options: point to a prebuilt image, or point to a Dockerfile, which flyctl will use to build an image. ### Build from a Dockerfile To build the image from a Dockerfile named `Dockerfile`, indicate the current working directory using the `` argument. ```bash theme={null} fly machine run . ``` Use the `--dockerfile` option to specify a Dockerfile with a different name. For example: ```bash theme={null} fly machine run . --dockerfile Dockerfile-dev ``` Any source files the Dockerfile uses should be present in the working directory. Once built, the image is pushed to the Fly.io Docker registry where your organization's remote builders can access it. ### Use an existing image For example: ```bash theme={null} fly machine run ghcr.io/livebook-dev/livebook:0.11.4 ``` ## Get a shell on a temporary Machine The following command creates a temporary Machine using the Dockerfile in the working directory, and logs you into an interactive shell on it: ```bash theme={null} fly machine run . --shell ``` If you don't specify an app, flyctl creates or reuses an app for interactive shells. When you log out of the shell, the Machine is deleted; the app is retained for future shells. The default shell is Bash. The `--command` flag allows you to specify a different shell if Bash isn't present in the Machine's Docker image. Log in as a non-`root` user using the `--user` flag. If you just want a shell on a temporary Ubuntu Machine that's in your org's private network, omit the `` argument: ```bash theme={null} fly machine run --shell ``` ## Run with a custom ENTRYPOINT or CMD You can have the Fly.io `init` override the ENTRYPOINT and CMD (if any) of the Machine's Docker image. ### Custom CMD Override CMD by including the command to run at the end of the `fly machine run` invocation. This sets the [`config.init.cmd`](/machines/api/machines-resource#machine-config-object-properties) property on the Machine. This example simply spins up a Debian Linux Machine with a `sleep` task to keep it awake; you can shell into it or whatever: ```bash theme={null} fly machine run debian sleep inf ``` ### Custom ENTRYPOINT Override ENTRYPOINT with the `--entrypoint` option. This sets the [`config.init.entrypoint`](/machines/api/machines-resource#machine-config-object-properties) property on the Machine. In this example we use the [`--file-local` option](/machines/flyctl/fly-machine-run#copy-a-local-file-into-the-machine-file-system) to send an entrypoint script to the Machine and `--entrypoint` to run the script before continuing to the custom CMD `sleep inf`: ```bash theme={null} fly machine run debian --file-local /entrypoint.sh=./entrypoint.sh \ --entrypoint "/entrypoint.sh" \ sleep inf ``` Here's a trivial `entrypoint.sh` you can use to test the above example: ```bash theme={null} #! /bin/bash echo "Hello from my Fly Machine" exec "$@" ``` The line "Hello from my Fly Machine" should appear in the app's logs. ## Set Machine resources Include one or more of the following options to use non-default specifications for the Machine to be run: ``` --vm-cpu-kind string The kind of CPU to use ('shared' or 'performance') --vm-cpus int Number of vCPUs --vm-gpu-kind string If set, the GPU model to attach (a100-pcie-40gb, a100-sxm4-80gb) --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 ``` These flags set [`config.guest`](/machines/api/machines-resource#machine-config-object-properties) properties. ## Set environment variables Specify environment variables to be available on the Machine with the `--env` flag, using NAME=VALUE pairs. This flag sets the [`config.env`](/machines/api/machines-resource#machine-config-object-properties) property on the Machine. Example: ```bash theme={null} fly machine run . --env MY_VAR=MY_VALUE \ --env MY_OTHER_VAR="my spacey value" \ --app my-app-name ``` Use quotes around the value if it has spaces in it. For sensitive environment variables, [set secrets on the app](/flyctl/cmd/fly_secrets) instead. ## Define a Fly Proxy network service The `--port` option defines a network service to allow the Fly Proxy to reach a local service on the Machine. This option gives you access to basic service configuration; the [Machines API](/machines/api/machines-resource) and [Fly Launch](/launch/index) both offer more control over the Machine's [`config.services`](/machines/api/machines-resource#machine-config-object-properties) properties. Map any external ports, where the proxy accepts requests directed at the app, to the internal port where the service is listening on IPv4. For each port combination, specify the protocol and [connection handler(s)](/networking/services#connection-handlers), using this format: ```plain theme={null} port[:machinePort][/protocol[:handler[:handler...]]] ``` For example, if your Machine runs a server on port 80, and the Fly Proxy should handle HTTP connections on port 80 and HTTPS connections on port 443, the port configuration would look like this: ```bash theme={null} fly machine run . --port 80/tcp:http \ --port 443:80/tcp:http:tls \ --app my-app-name ``` > [!NOTE] > > **Important:** If Machines within the same Fly App host different services, use different external ports so that they don't receive requests meant for another Machine. ## Set Fly Proxy autostop/autostart The `--autostart` and `--autostop` flags only work on Machines with Fly Proxy services configured. Learn more about [how Fly Proxy autostop/autostart works](/reference/fly-proxy-autostop-autostart) and [how to configure it](/launch/autostop-autostart). In a Machine service's configuration, `autostop` and `autostart` settings are optional. If the `--autostop` flag is absent in a `fly machine run` command, the Machine's [`config.services.autostop`](/machines/api/machines-resource#machine-config-object-properties) value doesn't get set, and the Fly Proxy does not shut the Machine down, even when there is no traffic to it. If the `--autostart` flag is absent in a `fly machine run` command, the Machine's [`config.services.autostart`](/machines/api/machines-resource#machine-config-object-properties) value doesn't get set, and the Fly Proxy does not start it in response to requests. The `--autostart` and `--autostop` flags set the value of `autostart` or `autostop` to `true` by default; you can explicitly set the value to `false`. For example, the following runs a new Machine that may be stopped by the Fly Proxy, but will never be restarted by it: ```bash theme={null} fly machine run nginx --port 80:80/tcp:http \ --port 443:80/tcp:http:tls \ --autostop \ --autostart=false ``` If you define more than one service on the Machine, and also use one or both of these flags, the setting applies to all the services. ## Stop or restart the machine on process exit Set the Machine's [restart policy](/machines/guides-examples/machine-restart-policy) using the `--restart` option. Options are `no`, `always`, and `on-fail`, which correspond to `no`, `always`, and `on-failure` values for the Machine [`config.restart.policy`](/machines/api/machines-resource#machine-config-object-properties) property. The default restart policy for a Machine created using `fly machine run` is `always`, unless you use the [`--rm` option](#destroy-the-machine-when-it-exits), in which case the default is `no`. ## Destroy the Machine when it exits By default, when a Machine is `stopped`, its file system is reset using its config and Docker image, and it sits ready to be `started` again. Use the `--rm` flag to cause the Machine to instead be destroyed when it stops. The default [restart policy](#stop-or-restart-the-machine-on-process-exit) for a Machine created with `fly machine run --rm` is `no`, to ensure that flyd doesn't ignore the exit code and restart the Machine. ## Mount a Fly Volume A Fly Volume is a slice of an NVMe drive attached to the hardware that runs the Machine. Create a volume before creating the Machine. ```bash theme={null} fly volume create --size 10 data_volume --region arn ``` Create the new Machine in the same region, using the volume name: `--volume :`. ```bash theme={null} fly machine run . --volume data_volume:data --region arn ``` Or by id: `--volume :`. ```bash theme={null} fly machine run . --volume vol_d42652p88kdw9l7r:data --region arn ``` A Machine can only mount one volume, and each volume can only be mounted on one Machine. To release a volume that is attached to a Machine, destroy the Machine. The `--volume` flag on the `fly machine run` command sets a subset of the properties of the Machine's [`config.mounts`](/machines/api/machines-resource#machine-config-object-properties) object. ## Add metadata to the Machine The Fly.io platform uses specific metadata, stored in a Machine's config, for its own purposes, such as assigning Machines to process groups. You can add custom metadata as well. The following starts a Machine that the `fly deploy` command will try to manage as part of the `app` process group, replacing its image and config with what, if anything, you have set up in the working directory for that app. ``` fly machine run . --metadata fly_platform_version=v2 \ --metadata fly_process_group=app \ --metadata my_metadata=mineallmine ``` You can see the [metadata in the Machine config](/machines/api/machines-resource#machine-config-object-properties): ```bash cmd theme={null} fly machine status -d -a my-app-name ``` ```out out theme={null} ... "metadata": { "fly_platform_version": "v2", "fly_process_group": "app", "mymeta": "mineallmine" }, ... ``` ## Place data into files on the Machine The [`files` property](/machines/api/machines-resource#machine-config-object-properties) of a Machine's configuration can be used to place data or secrets into files on the Machine file system. > [!NOTE] > > **Important:** This won't work for large files. There's a limit to how much data can be stored in an app secret or a Machine's configuration. ### Copy a local file into the Machine file system Use the `--file-local` flag to copy a local file onto the Machine at your specified path: ``` fly machine run . --file-local /path/inside/machine=local/path ``` flyctl Base64-encodes the file contents and stores the result in the `files.raw_value` property of the Machine's config; `/path/inside/machine` is stored in `files.guest_path`. When the Machine is created, the data is decoded and written to the file. ### Pass data in on the command line Place data into a file at your specified path, via an argument of the `--file-literal` flag: ```bash theme={null} fly machine run . --file-literal /path/inside/machine="Some text I want in a file" ``` In a shell session on the Machine: ```bash theme={null} root@2865553aedd268:/# cat /path/inside/machine Some text I want in a file ``` If your data isn't a simple string like in the above example, you can Base64-encode it first, and have your app code decode the contents of the file into the original format: ```bash theme={null} fly machine run . --file-literal /b64file=SGVsbG8hIEknbSBGcmFua2llIHRoZSBiYWxsb29uIQo= ``` In a shell session on the Machine: ```bash theme={null} root@1857779a44d108:/# cat b64file | base64 --decode Hello! I'm Frankie the balloon! ``` flyctl Base64-encodes the data and stores the result in the `files.raw_value` property of the Machine's config; `/path/inside/machine` is stored in `files.guest_path`. When the Machine is created, the data is decoded and written to the file. ### Make a secret available in a file [Fly Secrets](/apps/secrets) are stored in an encrypted vault, and by default they are available as environment variables on each of the app's Machines. You can make a secret available in a file, rather than an environment variable. Encode the data in Base64 format and put it into an app secret with `fly secrets set` or `fly secrets import`. Use the `--stage` flag to prevent flyctl from initiating a deployment once the secret is registered. > [!NOTE] > > **Important:** The secret must be Base64-encoded. If you try this with a secret that is not Base64-encoded, Machine creation fails. Example with a simple secret: ```bash theme={null} fly secrets set \ MY_BASE64_SECRET=SGVsbG8hIEknbSBGcmFua2llIHRoZSBiYWxsb29uIQo= \ --stage ``` Use the `--file-secret` flag when creating the Machine with `fly machine run`. In this example I'm putting the contents of the secret called `MY_BASE64_SECRET` into a file `/secret-file` on my new Machine: ```bash theme={null} fly machine run . \ --file-secret /secret-file=MY_BASE64_SECRET ``` The secret is available in the specified file, and not in an environment variable, on that Machine. It's decoded from Base64 into plain text. ``` root@1857770b4e10e8:/# cat secret-file Hello! I'm Frankie the balloon! ``` It can be useful to store multiple key-value pairs in a single secret. The following command Base64-encodes the contents of the text file `local-secrets` and registers the Base64-encoded string as the value of the secret `MY_SECRETS` on the app: ```bash theme={null} fly secrets set MY_SECRETS="$(base64 < local-secrets)" --stage ``` Run a new Machine with the `MY_SECRETS` secret available in a file (`/secret-file`): ```bash theme={null} fly machine run ubuntu sleep inf --file-secret /secret-file=MY_SECRETS ``` Check it in a shell session: ```bash theme={null} root@d891116b465018:/# cat secret-file USER="my_name" PASSWORD="1a2s3d4f" MACARON="macaroon in French" ``` If a particular process or user on the Machine should not have access to the secret, you can use an entrypoint script to change permissions on the secret file. > [!WARNING] > > **Warning:** This is not a way to hide secret values from members of an app's organization who have deployment privileges. Access via `fly ssh` commands is root access. All secrets that are set on an app are available, as either env vars or a file, in any Machine that gets updated after the secret is set. In the case of secrets, the data itself is not stored in the Machine config; instead, the name of the secret is stored in the `files.secret_name` config property and when the Machine is created, that secret is retrieved from the vault and its decoded value is written to the file. ## Create a standby Machine For the sake of resilience, you can create a stopped [standby](/apps/app-availability#standby-machines-for-process-groups-without-services) for Machines that don't have Fly Proxy services and therefore can't be supplemented by Fly Proxy "autostart" in case of a host failure. ```bash theme={null} fly machine run . --standby-for 287444ec026748,148ed726c54768 ``` Standby Machines normally remain stopped unless the watched Machines are affected by a host failure. To allow a standby Machine to be started, you can clear its standby configuration [with `fly machine update`](/machines/flyctl/fly-machine-update#make-a-standby-machine-a-normal-machine). The `--standby-for` flag sets the [`config.standbys`](/machines/api/machines-resource#machine-config-object-properties) Machine property. ## Start a Machine on a schedule Use the `--schedule` flag to set the Machine's [`config.schedule`](/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. > [!NOTE] > > **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. # Update a Machine Source: https://docs.fly.io/machines/flyctl/fly-machine-update Update an existing Fly Machine with fly machine update to change its image, CMD, resources, services, env vars, mounts, files or standby status on Fly.io. The [`fly machine update`](/flyctl/cmd/fly_machine_update) command updates the configuration of an individual, existing Fly Machine. Many, but not all, [Machine configuration](/machines/api/machines-resource#machine-config-object-properties) options are available to the `fly machine update` command through flags. The available flags are listed in the flyctl help and on the [`fly machine update` reference page](/flyctl/cmd/fly_machine_update). ## What it does Here's what `fly machine update` does for you: * Optionally, builds a new image and pushes it to the Fly.io registry. * Compares the Machine's existing configuration and confirms the changes you've asked for by passing it flags. * Composes a complete Machine configuration using the existing config plus your changes and passes this to the Machines update API endpoint. * Recreates the Machine with the new config, and, by default, starts it. * By default, waits for the Machine to start, and for any configured health checks to pass, before declaring success (or failure). ## Usage Here's the usage of `fly machine update`: ```bash theme={null} fly machine update [machine_id] [flags] ``` You can get the `machine_id` with [`fly machine list`](/flyctl/cmd/fly_machine_list). You can omit the `machine_id` if an app name is available from a `fly.toml` file in the working directory, or if the `--app` flag is passed with the `fly machine update` command. ## Select a Machine to update If you provide a `machine_id`, you don't have to provide an app name. If `fly machine update` finds an app name, either inside a `fly.toml` config file in the working directory or through the use of the `--app` flag, then you are presented with a selector in the CLI and can choose a Machine to act on. ### Build from a Dockerfile Use the `--dockerfile` option to build the image for the Machine from a Dockerfile. For example: ```bash theme={null} fly machine update --dockerfile Dockerfile ``` Any source files the Dockerfile uses should be present in the working directory. Once built, the image is pushed to the Fly.io Docker registry where your organization's remote builders can access it. ### Use an existing image Use the `--image` flag to specify an existing container image. For example: ```bash theme={null} fly machine update --image ghcr.io/livebook-dev/livebook:0.11.4 ``` ## Set the timeout to wait for the Machine to successfully update The `--wait-timeout` flag tells the `fly machine update` command how many seconds to wait for individual machines to transition states and for any health checks to pass. The default is 300. ## Use a custom ENTRYPOINT or CMD You can have the Fly.io `init` override the ENTRYPOINT and CMD (if any) of the Machine's Docker image. ### Custom CMD Set the [`config.init.cmd`](/machines/api/machines-resource#machine-config-object-properties) property with the `--command` flag. This example replaces CMD with a `sleep` task: ```bash theme={null} fly machine update --command sleep inf ``` ### Custom ENTRYPOINT Set the [`config.init.entrypoint`](/machines/api/machines-resource#machine-config-object-properties) property with the `--entrypoint` option. See [Run a new Machine](/machines/flyctl/fly-machine-run#custom-entrypoint) for an example of usage. ## Set Machine resources As for [`fly machine run`](/machines/flyctl/fly-machine-run#set-machine-resources). ## Set environment variables As for [`fly machine run`](/machines/flyctl/fly-machine-run#set-environment-variables). ## Define a Fly Proxy network service As for [`fly machine run`](/machines/flyctl/fly-machine-run#define-a-fly-proxy-network-service). ## Set Fly Proxy auto start and auto stop As for [`fly machine run`](/machines/flyctl/fly-machine-run#set-fly-proxy-autostop-autostart). ## Stop or restart the Machine on process exit As for [`fly machine run`](/machines/flyctl/fly-machine-run#stop-or-restart-the-machine-on-process-exit). ## Destroy the Machine when it exits As for [`fly machine run`](/machines/flyctl/fly-machine-run#destroy-the-machine-when-it-exits). ## Add metadata to the Machine As for [`fly machine run`](/machines/flyctl/fly-machine-run#add-metadata-to-the-machine). ## Change the mount point of an attached volume You can't detach a volume from an existing Machine, nor attach a new one. You can change the mount point of an attached volume using the `--mount-point` flag. This sets the Machine's [`config.mounts.path`](/machines/api/machines-resource#machine-config-object-properties) property. ## Place data into files on the Machine As for [`fly machine run`](/machines/flyctl/fly-machine-run#place-data-into-files-on-the-machine). ## Make the Machine a stopped standby As for [`fly machine run`](/machines/flyctl/fly-machine-run#create-a-standby-machine). ## Make a standby Machine a normal Machine If a Machine is configured as a standby, it doesn't start unless one of its watched Machines experiences host failure. To clear a Machine's standby configuration and allow it to be started under other conditions, use the `--standby-for` flag with an empty string: ```bash theme={null} fly machine update --standby-for "" ``` ## Start the Machine on a schedule As for [`fly machine run`](/machines/flyctl/fly-machine-run#start-a-machine-on-a-schedule). # Machines Guides and Examples Source: https://docs.fly.io/machines/guides-examples Browse guides and examples for Fly Machines, the fast-launching VMs on Fly.io, and find practical ways to put Machines to work in your own projects. Examples on how to use Machines in fun ways. # Run User Code on Fly Machines Source: https://docs.fly.io/machines/guides-examples/functions-with-machines Build functions-as-a-service on Fly Machines: create an isolated app, launch user-code Machines in any region via the API, then stop or delete them. [Fly Machines](/machines/overview) are [Firecracker VMs](https://firecracker-microvm.github.io/) with a fast REST API that can boot instances in about 300ms, in any region supported by [Fly.io](/reference/regions). One use for Machines is to run user code to extend your service, or as a service in itself. People sometimes call this **Functions-as-a-Service (FaaS)**. User code is fraught with peril. Fly Machines run it safely - even the most awful, buggy, and downright hostile user code. Most FaaS work similarly. The general idea: package up users' code, define a runtime environment, launch the code in a VM, then turn the VM off when it idles to save on compute bills. All while charging users per request! Packaging user code as a Docker image is mostly up to you. The rest, we can help with. ## Working with the Machines API Machines are operated over a simple REST API. First, visit the [Machines documentation](/machines/overview) to get ready for this guide. ## Grab or create a runtime Docker image Your function-as-a-service needs a well-defined runtime environment that can boot user code and then shut it down when it's idle. Here we'll use a [simple Fastify server](https://github.com/fly-apps/fastify-functions) running behind a small Golang HTTP proxy that exits after a few minutes of inactivity. ## Create an app Pick a unique name and create the required application to group machines together. This application is also the gateway to accessing the machines from the internet. ``` curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps" \\ -d '{ "app_name": "my-app-name", "org_slug": "personal", "network": "my-app-name-network" }' ``` **Status: 201** Note the `network` field. This is necessary to isolate the app from other private networks on the organization. Learn more about [custom private networks](/networking/custom-private-networks). Allocate an IP address to the app. This makes our app accessible at the IP, and via `my-app-name.fly.dev`. ```bash theme={null} fly ips allocate-v4 -a my-app-name ``` ## Launch a new machine Launch a user machine by supplying a Docker image and a networking configuration. ``` curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" \\ -d '{ "name": "quirky-machine", "config": { "image": "flyio/fastify-functions", "env": { "APP_ENV": "production" }, "services": [ { "ports": [ { "port": 443, "handlers": [ "tls", "http" ] }, { "port": 80, "handlers": [ "http" ] } ], "protocol": "tcp", "internal_port": 8080 } ], "checks": { "httpget": { "type": "http", "port": 8080, "method": "GET", "path": "/", "interval": "15s", "timeout": "10s" } } } }' ``` **Status: 200** Machines are permanent, so keep the ID from the response handy. Your users' service will boot immediately after this API call. Launch speed is a function of image size. Tiny images should boot in less than 2 seconds. Large, complex images are much slower to boot. Try accessing your new machine at `https://my-app-name.fly.dev`. If no requests arrive within 120 seconds ([default](https://github.com/fly-apps/fastify-functions/blob/0f4ef94451/start.sh#L3) but configurable), the machine will exit, but a request will force it to start back up automatically. ## Launch a Machine in another region That machine was launched in the region nearest to you. Say you want your user function to run fast for users in Australia. Let's launch another machine there. The Anycast IP you allocated will route traffic to the nearest region with a deployed machine. If it's stopped, it will start up automatically. ``` curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" \\ -d '{ "name": "machine-syd", "config": { "image": "flyio/fastify-functions", "env": { "APP_ENV": "production" }, "services": [ { "ports": [ { "port": 443, "handlers": [ "tls", "http" ] }, { "port": 80, "handlers": [ "http" ] } ], "protocol": "tcp", "internal_port": 8080 } ] }, "region": "syd" }' ``` **Status: 200** ## Stop a machine In a FaaS environment, you'll usually want to wait for a machine to exit. You can stop them with an API call, however, if needed. ``` curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/d5683210c7968e/stop" ``` **Status: 200** ```json theme={null} { "ok": true } ``` ## Start an existing machine Once created, a machine may be stopped and started until you delete it. The good news is, images stay cached after first launch. Starting an existing machine is very fast. Use the existing machine ID again to start it: ``` curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/d5683210c7968e/start" ``` **Status: 200** ```json theme={null} { "previous_state": "stopped" } ``` The start API call only returns successfully if the machine is in a stopped state. It's safe to call start on a running machine, though. If you're unsure of the machine state, just call start and ignore the error response. Start operations clear filesystem changes. The user process comes up with a fresh runtime environment. It is safe to share machines between users, as long as you stop them before they get reassigned. ## Get details about the deployment Now we have a couple machines running, we can use `fly logs -a my-app-name` to see what's going on. Also, for an overview: ```bash cmd theme={null} fly status -a my-app-name ``` ```out out theme={null} App Name = my-app-name Owner = personal Hostname = my-app-name.fly.dev Machines ID NAME REGION STATE CREATED 10e286064b1867 machine-syd syd started 2022-05-20T17:07:36Z 0e286e40cee686 quirky-machine cdg stopped 2022-05-20T17:07:04Z ``` Here we see that one of the Machines has exited. The closest equivalent API call lists all Machines in an app: ``` curl -i -X GET \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" ``` **Status: 200** ## Delete a machine When you're done, delete the machine: ``` curl -i -X DELETE \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/d5683210c7968e" ``` **Status: 200** ```json theme={null} { "ok": true } ``` ## Additional notes The Machines API is young and full of potential. We haven't worked everything out, so please [post on our forums](https://community.fly.io/) with questions! Here are a few things worth noting: The *wake-on-request* behavior described above will be unpredictable when running more than one machine per region. Standard `flyctl` operations like `deploy`, `scale`, `autoscaling` aren't currently supported by Machines. `ssh` is supported. You can SSH to a machine using its IPv6 address (from `fly machines list`): ```bash theme={null} fly ssh console 'your:machines:ipv6:address:0:0:0:0' ``` Or you can select a machine to connect to from a list using `fly ssh console -s`: ```bash theme={null} fly ssh console -a my-app-name -s ? Select instance: [Use arrows to move, type to filter] > sjc (fdaa:0:6787:a7b:a15f:ee6a:3882:2) sjc (fdaa:0:6787:a7b:a15f:c306:4566:2) sjc (fdaa:0:6787:a7b:a15f:1a8c:2c01:2) ``` # Machine Placement and Regional Capacity Source: https://docs.fly.io/machines/guides-examples/machine-placement Avoid failed scaling when a Fly.io region is out of capacity: give fly machine clone a prioritized region list or aliases like us, eu, and apac. > [!NOTE] > > **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` and `gru`, 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. ### Where can you deploy? Run `fly platform regions` to see the regions where Fly.io has datacenters, along with their region codes. Use those codes to set region preferences for your Machines. ### 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. > [!NOTE] > > Want to see where your app might run? > Here's the [current list](/reference/regions): ams, arn, cdg, dfw, ewr, fra, gru, iad, jnb, lax, lhr, nrt, ord, sin, sjc, syd, yyz. ### Related reading * [An Introduction to Fly Machines](/machines/overview) * [Regions Reference](/reference/regions) * [Fly Machines API](/machines/api) * [Get Regions](/api/machines/platform/get-regions) # Machine restart policy Source: https://docs.fly.io/machines/guides-examples/machine-restart-policy Compare no, always, and on-fail Machine restart policies, check one with fly m status -d, and change it via fly m update, the API, or fly.toml. The Machine restart policy defines whether and how flyd restarts a Machine after its main process exits. The restart policy applies per Machine, and is not an app-wide setting. The restart policies are: * **`no`**: Never try to restart a Machine automatically when its main process exits, whether that’s on purpose or on a crash. `no` is the default when you use the `--rm` option to create a Machine with `fly m run` that auto-destroys on exit. * **`always`**: Always restart a Machine automatically and never let it enter a `stopped` state, even when the main process exits cleanly. `always` is the default when you create a Machine with `fly m run` and for Fly Postgres app Machines. Recommended for "always-on" apps with no services configured, since the Machine restarts regardless of the exit code. * **`on-fail`** (or **`on-failure`** for the Machines API and when viewed in the Machine config): Try up to `max_retries` (default 10) times within a 5-minute window to automatically restart the Machine if it exits with a non-zero exit code, before letting it stop. Recommended for most Machines with services configured, since Fly Proxy can wake them on request. `on-fail` lets Machines be restarted if they crash, and allows your app Machines to effectively scale down by exiting cleanly. `on-fail` is the default when there's no explicit restart policy in a Machine's config, such as Machines created by `fly launch` and `fly deploy`. Machines with a schedule also default to the `on-fail` restart policy. ## Check a Machine's restart policy Display a Machine's status and its config in `json` format: ```bash theme={null} fly m status -d ``` Example output with a restart policy of `always`: ```out theme={null} ... Config: { "init": {}, "image": "registry-1.docker.io/flyio/hellofly:latest", "restart": { "policy": "always" }, "guest": { "cpu_kind": "shared", "cpus": 1, "memory_mb": 256 }, "dns": {} } ``` ## Change a Machine's restart policy with flyctl Update the Machine config: ```bash theme={null} fly m update --restart ``` The following example updates a Machine's restart policy to `on-fail`: ```bash cmd theme={null} fly m update 3908032c794088 --restart on-fail ``` ```out out theme={null} Configuration changes to be applied to machine: 3908032c794088 (my-app-name)    ... // 2 identical lines    "image": "registry-1.docker.io/flyio/hellofly:latest",    "restart": { -  "policy": "always" +  "policy": "on-failure"    },    "guest": {    ... // 6 identical lines ? Apply changes? (y/N) ``` Enter `y` to apply the changes. ## Change a Machine's restart policy with the Machines API With the Machines API, you can set the restart policy, and the maximum number of retries when the policy is `on-failure`. > [!NOTE] > > **Important:** The API and the returned Machine config use `on-failure` instead of `on-fail`. Endpoint: `POST /apps/{app_name}/machines/{machine_id}` For example: ``` ... "restart": { "max_retries": 5, "policy": "on-failure" }, ... ``` Refer to the Machines API docs for more information about [updating a Machine](/machines/api/machines-resource#update-a-machine). ## Set a restart policy in your fly.toml You can also set a default app-level restart policy in your fly.toml file: ```toml theme={null} [[restart]] policy = "" retries = 10 processes = ["app"] ``` A restart policy can be targeted to a specific process group. If a group is not specified, all machines in an app will have the same default restart policy. If needed, you can still apply different policies on individual machines using the flyctl or Machines API methods above. # Machine Sizing Source: https://docs.fly.io/machines/guides-examples/machine-sizing Size Fly Machines on Fly.io with CPU and memory presets or custom values via the Machines API or fly machine run, see the memory limits, and resize later. Here are a few ways to set the size (CPU and memory) of your Machine VMs. This guide describes creating and sizing individual Machines with the Machines API and `fly machine` commands. For Fly Launch apps, the same general sizing rules apply, but you can also use `fly scale` commands to [update groups of Machines](/launch/scale-machine) or set a [default VM size](/reference/configuration#the-vm-section) in the `fly.toml` configuration file. ## General Rules We have some [preset sizes](/about/pricing#started-fly-machines) for you to choose from. Each CPU selection defaults to a specific amount of memory. You can, however, scale your machine's CPU and memory separately. Memory limits are `2gb * shared CPU size` or `8gb * performance CPU size`. Minimum memory is `256m * shared CPU size` or `2048m * performance CPU size`. **Examples:** * If you have 4 shared CPUs, the max memory you can add is `2048 * 4 = 8192`. * If you have 4 performance CPU's, the max memory you can add is `8192 * 4 = 32768`. ## Using Size Presets Any size listed for Machines on the [pricing page](/about/pricing#started-fly-machines) can be used when creating a Machine. Here's what that looks like to launch a `performance-2x` machine (2 CPU, 4gb memory): ```bash theme={null} curl -i -X POST \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ "http://${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" \ -d '{ "name": "savvy-machine", "config": { "image": "some-savvy-image", "size": "performance-2x" } }' ``` If you're using `flyctl`, the equivalent command looks like this: ```bash theme={null} fly machine run \ -a my-app-name \ --name savvy-machine \ --vm-size performance-2x \ some-savvy-image ``` ## Custom Sizes When creating a machine, you can define the CPU and memory sizes yourself. Memory must be a multiple of 256 for shared sizes, and 2048 for performance sizes. Here we create a machine with a 2 CPUs but double the RAM you'd get if using size `shared-cpu-2x`. ```bash theme={null} curl -i -X POST \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ "http://${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines" \ -d '{ "name": "savvy-machine", "config": { "image": "some-savvy-image", "guest": { "cpu_kind": "shared", "cpus": 2, "memory_mb": 1024 } } }' ``` The `cpu_kind` parameter can be one of `shared` or `performance`. Learn more about CPU types and performance [here](/machines/cpu-performance). If you're using `flyctl`, the equivalent command looks like this: ```bash theme={null} fly machine run \ -a my-app-name \ --name savvy-machine \ --vm-cpus 2 \ --vm-memory 1024 \ some-savvy-image ``` There is not yet a CLI flag for `shared` vs `performance`. It defaults to `shared` CPU types. ## Adjusting Machine Size You can adjust CPU and memory by [updating a Machine](/machines/api/machines-resource#update-a-machine). Updating a machine replaces the VM with a new one, but machine ID stays the same. If we create a machine with the same command as above (using 1024m memory), but later want to reduce that to 512m (the minimum allowed), we can make the following API call: ```bash theme={null} # Updating a machine requires the *entire* config # so treat this as an example curl -i -X POST \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ "http://${FLY_API_HOSTNAME}/v1/apps/my-app-name/machines/73d8d46dbee589" \ -d '{ "config": { "image": "some-savvy-image", "guest": { "cpu_kind": "shared", "cpus": 2, "memory_mb": 512 } } }' ``` If you're using `flyctl`, the equivalent command looks like this: ```bash theme={null} fly machine update \ -a my-app-name \ -i some-savvy-image \ --vm-cpus 2 \ --vm-memory 512 \ 73d8d46dbee589 ``` # Run Machines with flyctl Source: https://docs.fly.io/machines/guides-examples/machines-app-using-flyctl > [!WARNING] > > **This document is stale.** > > This document illustrates some ways to work with Fly Machines individually using flyctl, and was written before Fly Apps V2 made it possible to manage Machines as a group, using a `fly.toml` config file and `fly deploy`. For most web apps, the [Fly Apps docs](/apps) are a better place to start! We talk up the Machines API for running user code, for ephemeral jobs, or both. Fly Machines are also the VM building blocks for V2 of the [Fly.io app-platform-as-a-service offering](/launch/index). flyctl provides commands for managing individual Machines from the CLI, on a level more granular than `fly deploy`ing an app. The [`fly machine` command docs](/flyctl/cmd/fly_machine) are another good resource. ## Elements of a Machines Web App To get a single Machine running a stateless app, and serving it to the world at `.fly.dev`, you need to assemble the following pieces: 1. A Machines-flavoured [Fly App](/apps/overview)—you're not using `fly deploy` to manage the app's Machines, but you still need an app, as it's the administrative entity that owns your VMs and things like anycast IPs and volumes 2. At least an IPv4 public anycast IP address 3. A port mapping for the HTTP service you want to expose 4. A Docker image We cook up VMs starting from Docker images, so you need to tell the platform how to get, or build, an image. None of this is special for Machines; if we haven't built a bells-and-whistles launcher for your kind of project, then you have to do it for V1 (Nomad) Apps launched with `fly launch` too. The big difference here is that instead of putting an app-wide configuration into a `fly.toml` file and running `fly deploy`, we'll incorporate per-VM config into the `fly m run` (AKA `fly machine run`) command that creates the VM. Imagine you have a Dockerfile in the current working directory, along with maybe some source code that Dockerfile draws on; and when you build it, you get an image for something that listens on internal port 8080. You want that to be reachable from the world on port 443, where browsers send HTTPS connections. If you like to do things in an orderly fashion, you might want the App to have public IP addresses before you boot the Machine: ``` fly apps create --machines --name testrun fly ips allocate-v4 --shared -a testrun fly ips allocate-v6 -a testrun fly m run . -p 443:8080/tcp:tls:http -p 80:8080/tcp:http --region yyz -a testrun ``` The last command configures and deploys the VM. See the `.` in there? That, in this case, is the path to the Dockerfile. Such a diminutive, easy-to-miss representation of *everything you want to supply for the VM to run*. Don't leave it out. Note the allocation of a [shared IPv4 address](/networking/services#shared-ipv4) above. For regular HTTP(S) web apps, shared IPv4 works fine, and helps conserve the world's dwindling IPv4 supply (and can [save you money](/about/pricing#anycast-ip-addresses)). If your app runs other services and you need a dedicated IPv4, you can provision one by leaving out the `--shared` flag. If your app doesn't exist yet, `fly m run` will prompt you to create it, so you can actually do this instead: ``` fly m run . -p 443:8080/tcp:tls:http -p 80:8080/tcp:http --region yyz -a testrun fly ips allocate-v4 --shared -a testrun fly ips allocate-v6 -a testrun ``` ## Example: A Simple Public Web App Let's make this more concrete with an example. I want to run a tiny Python/Flask app with almost-static content. While there's no need to go down to the level of controlling individual Machines for a simple web app—you'd just run `fly launch` to get a [Fly App](/apps) configured and have the conveniences of `fly deploy`—we can do it if we want to. Heads up! I'm also going to make a little mistake or two to help us learn our way around a Machines App. I'll get the code and Dockerfile from the repo: ```bash theme={null} git clone https://github.com/fly-apps/hello-gunicorn-flask ``` Then I'll go into the dir: ```bash theme={null} cd hello-gunicorn-flask ``` And just try it out: ``` fly apps create --machines --name testrun fly ips allocate-v4 --shared -a testrun fly ips allocate-v6 -a testrun fly m run . -p 443:8080/tcp:tls:http -p 80:8080/tcp:http --region yyz -a testrun ``` Once I get a message like this: ``` Success! A machine has been successfully launched in app testrun, waiting for it to be started Machine ID: 21781160b41d89 Instance ID: 01GMF1WWXBEQR0RSHAN7XVWPK4 State: created Machine started, you can connect via the following private ip fdaa:0:3b99:a7b:aa4:3c65:c2cc:2 ``` I can check on my app! ``` fly apps open -a testrun ``` …and, I get nothing. (Note that I'm appending `-a` to most of my flyctl commands, because we're not using a `fly.toml` for configuration, and a `fly.toml` in the working directory is where flyctl usually gets the app name that it thinks you're implying when you don't specify one.) ![Browser screenshot saying 'This page isn't working; testrun.fly.dev didn't send any data. ERR\_EMPTY\_RESPONSE'](https://mintcdn.com/fly-io/MzcU_lfZkOK0V9Fa/images/testrun-err-empty-response.webp?fit=max\&auto=format\&n=MzcU_lfZkOK0V9Fa\&q=85\&s=f14bf42de123124b8584fcee1b18010c) What can be wrong here? Start with the go-to `fly status -a testrun`: ```bash cmd theme={null} fly status -a testrun ``` ```out out theme={null} App Name = testrun Owner = personal Hostname = testrun.fly.dev Platform = machines ID STATE REGION HEALTH CHECKS IMAGE CREATED UPDATED 21781160b41d89 started yyz testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q 2022-12-17T03:26:30Z 2022-12-17T03:26:32Z ``` I have a VM `started`. I can look for more details about it using `fly m status`: ```bash cmd theme={null} fly m status 21781160b41d89 -a testrun ``` ```out out theme={null} Machine ID: 21781160b41d89 Instance ID: 01GMF1WWXBEQR0RSHAN7XVWPK4 State: started VM ID = 21781160b41d89 Instance ID = 01GMF1WWXBEQR0RSHAN7XVWPK4 State = started Image = testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q Name = lingering-shape-1541 Private IP = fdaa:0:3b99:a7b:aa4:3c65:c2cc:2 Region = yyz Process Group = Memory = 256 CPUs = 1 Created = 2022-12-17T03:26:30Z Updated = 2022-12-17T03:26:32Z Command = Event Logs STATE EVENT SOURCE TIMESTAMP INFO started start flyd 2022-12-16T22:26:32.888-05:00 created launch user 2022-12-16T22:26:30.338-05:00 ``` Everything looks fine here. How about logs: ```bash theme={null} fly logs -a testrun ``` I see a lot of messages from the proxy like `[info]Still waiting for machine to listen on 0.0.0.0:8080 (waited 43.329214343s so far)` and `error.message="failed to connect to fly machine: Gave up after 50 retries"`. So my Machine is started, but it's not listening where I've told the Fly Proxy it would be. If I scroll up closer to the start of the logs: ``` [INFO] Listening at: http://0.0.0.0:4999 (512) ``` Oh noes, I told Fly Proxy to send requests to internal port 8080 when all along, my process was listening on 4999! ## Update the Machine configuration If I were working with a [Fly App](/apps/overview) instead of with Machines directly, I'd edit the internal port in my `fly.toml` and redeploy. I'll do the Fly Machines equivalent: `fly m update` with the modification I want to make to the configuration. ```bash cmd theme={null} fly m update -p 443:4999/tcp:tls:http -p 80:4999/tcp:http 21781160b41d89 -a testrun ``` ```out out theme={null} Searching for image 'registry.fly.io/testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q' remotely... image found: img_3mno4w881q64k18q Image: registry.fly.io/testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q Image size: 54 MB Configuration changes to be applied to machine: 21781160b41d89 (lingering-shape-1541)    { ...    ... // 2 identical lines    {    "protocol": "tcp", -  "internal_port": 8080, +  "internal_port": 4999,    "ports": [    {    ... // 8 identical lines    {    "protocol": "tcp", -  "internal_port": 8080, +  "internal_port": 4999,    "ports": [    {    ... // 15 identical lines ? Apply changes? Yes Updating machine 21781160b41d89 No health checks found Machine 21781160b41d89 updated successfully! ==> Monitoring health checks No health checks found Monitor machine status here: https://fly.io/apps/testrun/machines/21781160b41d89 ``` The VM is restarted with the updated configuration. Now I try `fly apps open -a testrun` again, and voilà: ![The web app running in the browser. It says 'Hello from a Flask app on Fly.io; Or goodbye'. The word 'goodbye' is a hyperlink.](https://mintcdn.com/fly-io/MzcU_lfZkOK0V9Fa/images/testrun-hello.webp?fit=max\&auto=format\&n=MzcU_lfZkOK0V9Fa\&q=85\&s=702f2fe33b71dcb0edf1e6c151d16f2e) (If you're following along and have this example app deployed, try going to `https://.fly.dev/`) ## Update the Machine's Docker image Imagine a hypothetical future me, with several VMs running my app in different regions (to serve users around the world faster) sending a GET request to one of my App's VMs at its private IPv6 on the internal WireGuard network, and noticing that I haven't configured my process to listen on IPv6! To fix that, I'll have to change something inside the Docker image, and we may as well look at that now, while we're on the subject of fixing mistakes in Machines. Again, with a V1 App, I'd use `fly deploy` for this. Can we use `fly m update` to rebuild and restart a Machine? Why, yes, we can! ```bash cmd theme={null} fly m update 21781160b41d89 --dockerfile ./Dockerfile -a testrun ``` ```out out theme={null} ... --> Pushing image done Image: registry.fly.io/testrun:deployment-01GMPHMHN24ZCHR4Q0RW8QE99R Image size: 141 MB Configuration changes to be applied to machine: 21781160b41d89 (lingering-shape-1541)    ... // 6 identical lines    "tty": false    }, -  "image": "registry.fly.io/testrun:deployment-01GMPH92RA8WRFFTZH0RM1KQMJ", +  "image": "registry.fly.io/testrun:deployment-01GMPHMHN24ZCHR4Q0RW8QE99R",    "metadata": null,    "restart": {    ... // 26 identical lines ? Apply changes? Yes Updating machine 21781160b41d89 No health checks found Machine 21781160b41d89 updated successfully! Monitor machine status here: https://fly.io/apps/testrun/machines/21781160b41d89 ``` This is not horrible to do manually on three instances, but pretty tedious if you have 20. There's a better way, if all the VMs in the App are meant to be running from the same image: `fly image update`. I can build a new image and push it to the Fly.io image repository with: ```bash theme={null} fly deploy --image-label v2 --build-only --push -a testrun ``` I can skip giving it a label, but a sensible label makes the difference between doing ```bash theme={null} fly image update --image registry.fly.io/testrun:deployment-01GMHP0PH4NQD7EBTGXPNEFDJR -a testrun ``` and ```bash theme={null} fly image update --image registry.fly.io/testrun:v2 -a testrun ``` to update every VM on the App to the new image. ## Add an instance in another region ### By creating a new Machine I can create another Machine with `fly m run` using the same image as the others are running, on the same App, but in a different region (use the correct port this time): ```bash theme={null} fly m run registry.fly.io/testrun:v12 -p 443:4999/tcp:tls:http -p 80:4999/tcp:http --region syd -a testrun ``` ### By cloning a Machine Or I can `fly m clone` an existing instance for the same effect (this command is in flux and will probably make more sense in the future for general VM use): ```bash cmd theme={null} fly m clone 21781160b41d89 --region ord -a testrun ``` ```out out theme={null} Cloning machine 21781160b41d89 into region ord Provisioning a new machine with image registry.fly.io/testrun:v12... Machine 9080567add5087 has been created... Waiting for machine 9080567add5087 to start... No health checks found Machine has been successfully cloned! ``` At this point I've deployed my app, fixed the mistakes I made while deploying it, and scaled it out to several datacentres around the world. Here are a few other things that could come in handy while administering this app: ## Open a shell on a Machine We can do this using just flyctl and [user-mode WireGuard](https://fly.io/blog/our-user-mode-wireguard-year/), thanks to a program called [Hallpass](https://fly.io/blog/ssh-and-user-mode-ip-wireguard/) that runs on each Fly.io App VM. You can ssh into a specific Machine with `fly ssh console -s`: ```bash cmd theme={null} fly ssh console -s -a testrun ``` ```out out theme={null} ? Select VM: [Use arrows to move, type to filter] > ord: 9080567add5087 fdaa:0:3b99:a7b:8ba9:46d4:87d6:2 polished-mountain-3926 yyz: 21781160b41d89 fdaa:0:3b99:a7b:aa4:3c65:c2cc:2 lingering-shape-1541 lax: 9185340f472683 fdaa:0:3b99:a7b:7d16:958:b788:2 white-shape-8276 ``` If you can ssh into it, that's a sure sign of life. You can also make modifications from in there. Just be aware that anything you do to the root filesystem (i.e. not on a mounted Fly Volume or a separate database app) will be undone next time you start up the Machine. Note: You can only get a console interface on a VM if there's some sort of shell installed in your project. ## Scale Down Temporarily By Stopping a VM Use `fly m stop` to stop a Machine; `fly m start` to get it going again. There is a subtlety here where if you have a public service defined, if the Fly Proxy happens to send a request to a stopped Machine, it will try to get it started, too. One mitigation for this is to have Machines run a main process that exits after some time idle, thus shutting down the VM. Then if the proxy wakes a VM up just for some random bot hit, it won't stay up indefinitely. ## Scale Down Permanently Use `fly m stop`, then `fly m remove` to remove a Machines VM entirely. ## Delete the app If I want to yeet the whole App and all its Machines, IP addresses, Volumes, etc., I can run `fly apps destroy` on it, just like a V1 App. # Managing Machines with the Machines API Source: https://docs.fly.io/machines/guides-examples/managing-machines-with-the-api Machines API patterns: clone a Machine's config, set fly_process_group metadata so flyctl manages it, define services, and sync autostop/autostart. ![Illustration by Annie Ruygt of a big book about the Machines API](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/managing-machines-api.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=9c1dfc2d4309adc39b01a281ba7cc197) > [!NOTE] > > **The [Machines resource API](/machines/api/machines-resource) documents every endpoint. This guide covers the patterns you'll actually need when building on it: cloning machines, keeping them in sync with `flyctl`, configuring services, managing auto-stop/start behavior, and updating config.** If you're just deploying an app, use `fly deploy`. This guide is for when you need programmatic control, when you're building a platform, managing machines from your own code, or doing things `flyctl` doesn't support directly. ## Before you begin You need a Fly.io API token. Set it as an environment variable: ```bash theme={null} export FLY_TOKEN=$(fly tokens create deploy -a YOUR_APP) ``` Deploy tokens are scoped to a single app and are the safest option for API access. If you need broader access, for example to manage multiple apps, you can use an org-level token instead. Read more about tokens on [this page](/security/tokens). The Machines API base URL is `https://api.machines.dev/v1`. All requests need the `Authorization` header: ```bash theme={null} curl -H "Authorization: Bearer $FLY_TOKEN" \ https://api.machines.dev/v1/apps/YOUR_APP/machines ``` ## Create a Machine from an existing one The most reliable way to create a Machine via the API is to start from an existing Machine's configuration. This is especially useful if you need the new Machine to behave like one created by `fly launch` or `fly deploy`. Those commands set metadata, services, and other config that can be easy to miss if you build the JSON from scratch. The workflow: 1. **Get the source Machine's config.** Use the API or flyctl: ```bash theme={null} # Via API curl -H "Authorization: Bearer $FLY_TOKEN" \ https://api.machines.dev/v1/apps/YOUR_APP/machines/MACHINE_ID # Via flyctl fly machine status MACHINE_ID --display-config ``` 2. **Modify the JSON** for your needs — change the region, adjust resources, update environment variables. 3. **Create the new Machine** by POSTing the modified config: ```bash theme={null} curl -H "Authorization: Bearer $FLY_TOKEN" \ -H "Content-Type: application/json" \ -X POST https://api.machines.dev/v1/apps/YOUR_APP/machines \ -d @config.json ``` You can also use `fly machine run` with command-line flags, but if you're starting from a full JSON config, the API is more straightforward. ## Make API-created Machines visible to flyctl Machines created directly through the API are "detached" by default if you build the config from scratch or strip the metadata. They exist and run, but `fly deploy` and `fly scale` won't touch them since those commands only manage Machines that belong to a process group. Note that if you create a Machine by copying an existing flyctl-managed Machine's config, the `fly_platform_version` and `fly_process_group` metadata carry over, so the new Machine is automatically part of the process group. To bring a Machine under flyctl management, set two metadata keys: * `fly_platform_version`: must be `v2` * `fly_process_group`: the name of an existing process group (e.g., `app`, `worker`) With flyctl: ```bash theme={null} fly machine update MACHINE_ID \ --metadata fly_platform_version=v2 \ --metadata fly_process_group=app ``` Or include the metadata in the Machine config JSON when creating via the API: ```json theme={null} { "config": { "metadata": { "fly_platform_version": "v2", "fly_process_group": "app" } } } ``` Once set, the Machine becomes part of that process group. The next `fly deploy` will update it along with the rest of the group. Note that until the next deploy, the Machine won't show a VERSION in `fly status`. If you *want* Machines to stay independent of flyctl — for example, one-off workers or machines managed entirely by your own tooling, then leave the metadata off. Keep in mind that `fly status` will list them as detached. ## Configure services via the API In `fly.toml`, you define how traffic reaches your app using `[http_service]` or `[[services]]`. The API equivalent is the `services` property in the Machine config. The services config controls how the Fly proxy routes connections to your Machine: ports, protocols, concurrency limits, and auto-stop/start behavior. Here's a typical example: ```json theme={null} { "config": { "services": [ { "ports": [ { "port": 443, "handlers": ["tls", "http"] }, { "port": 80, "handlers": ["http"] } ], "protocol": "tcp", "internal_port": 8080, "concurrency": { "type": "connections", "hard_limit": 25, "soft_limit": 20 }, "autostop": "suspend", "autostart": true, "min_machines_running": 1 } ] } } ``` > [!NOTE] > > **Important:** Within a process group, every Machine must have identical service configuration. The Fly proxy expects consistency: if Machine A accepts connections on port 8080 with a hard limit of 25, and Machine B has different settings, routing behavior becomes unpredictable. If you're working from an existing Machine's config (as described above), the services are already there. Modify what you need, but keep them consistent across the group. ## Update a Machine's configuration The Machines API doesn't support `PATCH` — you can't send just the fields you want to change. Instead, the update flow is: 1. **GET** the full Machine config: ```bash theme={null} curl -H "Authorization: Bearer $FLY_TOKEN" \ https://api.machines.dev/v1/apps/YOUR_APP/machines/MACHINE_ID ``` 2. **Extract and modify** the `config` object from the response — add a service, change an environment variable, adjust resources. 3. **Wrap and POST** the modified config back. The GET response returns the config nested inside the Machine object, but the POST endpoint expects a `{"config": ...}` wrapper at the top level: ```bash theme={null} curl -H "Authorization: Bearer $FLY_TOKEN" \ -H "Content-Type: application/json" \ -X POST https://api.machines.dev/v1/apps/YOUR_APP/machines/MACHINE_ID \ -d @updated-config.json ``` Where `updated-config.json` looks like: ```json theme={null} { "config": { ...your modified config object... } } ``` Always start from a fresh GET. Don't cache old configs and POST them later because another process (or a `fly deploy`) may have changed the Machine's config in the meantime. **Note:** Updating a Machine's config triggers a stop/start cycle. It's not a hot reload. Plan for brief downtime when updating running Machines. ## Auto-stop, autostart, and suspend The Fly proxy can automatically stop or suspend idle Machines and restart them when requests arrive. This is configured per-Machine in the `services` property, the API equivalent of `auto_stop_machines` and `auto_start_machines` in `fly.toml`. The relevant fields in each service entry: | Field | Values | What it does | | - | - | - | | `autostop` | `false`, `true`, `"suspend"` | What happens when the Machine has no traffic and there's excess capacity. `false` disables autostop. `true` stops the Machine (cold start on restart). `"suspend"` suspends the Machine (preserves memory, faster resume). The API also accepts `"off"` and `"stop"` as aliases, but normalizes them to `false` and `true` respectively. | | `autostart` | `true`, `false` | Whether the proxy boots this Machine when a request arrives and no running Machine can serve it. Must be a boolean — the API rejects string values. | | `min_machines_running` | integer | Minimum Machines to keep running in the primary region, even when idle | ### How the proxy decides to stop a Machine The proxy tracks active connections. When a Machine has zero connections and other Machines in the same process group can handle the load, the proxy considers it excess capacity and triggers an autostop. The `concurrency.soft_limit` is the threshold. Once total connections across the group drop low enough that a Machine is unnecessary, it gets stopped or suspended. Even if a Machine is the only one in its region, it can still be stopped. If it has zero connections and `autostop` is enabled, the proxy will stop or suspend it. It will automatically start again on the next request if `autostart` is enabled. **Stop vs suspend:** Stopped Machines boot from scratch on restart, so expect a cold start. Suspended Machines preserve memory state and resume faster, but not all workloads support suspend cleanly ([Read more](/getting-started/troubleshooting#suspend-vs-stop) about common issues like clock skew after resume). ### Keep autostart and autostop in sync If you enable `autostop` but disable `autostart`, Machines will stop and never come back. Requests will return 503. If you enable `autostart` but disable `autostop`, Machines that get started will stay running indefinitely and never scale back down. In general, set both or neither. ### Long-running background work This is the most common gotcha. If your Machine receives an HTTP request, spawns a background task, and returns a response immediately, the proxy sees zero active connections and may autostop the Machine, killing the background work. For more information about background workers, check out [this guide](/blueprints/work-queues). Approaches that work: * **Hold the connection open.** Stream progress back to the client or use long-polling so the connection stays active while work runs. The proxy won't stop a Machine with active connections. * **Disable autostop for workers.** Set `autostop` to `"off"` and have your application call `process.exit()` (or equivalent) when work is complete. You manage the lifecycle; the proxy stays out of it. * **Use a separate process group.** Run workers as a different process group with `autostop: "off"` while your web-facing Machines use normal autostop. This way cost savings apply to the web tier without affecting background processing. ### Machines without services If a Machine has no `services` config (no HTTP or TCP services), the Fly proxy doesn't manage it at all. It won't auto-stop or auto-start — it just runs until you stop it via the API, or it exits on its own. Its lifecycle is governed entirely by its [restart policy](/machines/guides-examples/machine-restart-policy). This is the right setup for cron-style jobs, queue workers that poll for work, or anything that doesn't accept inbound connections. ### Setting a restart policy Machines without services manage their own lifecycle through restart policies. Set the restart policy in the Machine config: ```json theme={null} { "config": { "restart": { "policy": "on-failure", "max_retries": 5 } } } ``` Available policies: * `"no"` — don't restart on exit * `"always"` — always restart on exit * `"on-failure"` — restart only on non-zero exit, up to `max_retries` ## Operations not supported by the Machines API The Machines REST API covers Machine lifecycle and most app-level operations. A few peripheral operations still require the GraphQL API, such as those relating to organizations and provisioning custom networks. The GraphQL API is an internal interface used by `flyctl` with no stability guarantees, so it can change without notice. When possible, shell out to `flyctl` itself rather than hitting the GraphQL API directly. The CLI flags are a more stable interface than the underlying API. If you can't use `flyctl` (for example, your system can't execute shell commands during setup), you can reverse-engineer the GraphQL calls: ```bash theme={null} LOG_LEVEL=debug fly ``` This prints the full API communication, including GraphQL endpoints and payloads. You can copy these requests and make them from your own code. Keep in mind that they may break when `flyctl` updates. ## Related reading * [Machines API reference](/machines/api/machines-resource) — full endpoint documentation * [Working with the Machines API](/machines/api/working-with-machines-api) — authentication, rate limits, and connection details * [Machine states and lifecycle](/machines/machine-states) — how Machines move between states * [fly.toml configuration reference](/reference/configuration) — the flyctl-managed equivalent of API config # Multi-container Machines Source: https://docs.fly.io/machines/guides-examples/multi-container-machines Run sidecars in one Fly.io Machine with Pilot and the containers array, set dependencies and health checks, and deploy with Docker Compose, the API, or flyctl. 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`](/api/machines/models#fly-containerconfig). 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 theme={null} { "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 theme={null} "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 theme={null} "tcp": { "port": 6379 } ``` **HTTP health checks**: Makes HTTP requests to an endpoint ```json theme={null} "http": { "port": 80, "method": "GET", "path": "/", "scheme": "http" } ``` **Exec health checks**: Runs a command inside the container ```json theme={null} "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 Docker Compose If you already have a Docker Compose setup, you can deploy it to Fly Machines without rewriting your configuration. Fly builds and runs Compose services as containers within a single Machine. #### Requirements You need flyctl v0.3.152 or later: ```bash theme={null} fly version ``` If you're behind, update with `fly version update`. #### Configuration Add a `[build.compose]` section to your `fly.toml`: ```toml theme={null} [build.compose] ``` Fly auto-detects Compose files using the standard lookup order: `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`. If your file has a different name, specify it: ```toml theme={null} [build.compose] file = "docker-compose.prod.yml" ``` #### Routing traffic Set `internal_port` in your `fly.toml` `[[services]]` or `[http_service]` block to match the port exposed by the container that should receive traffic. Only one container handles inbound requests from the Fly proxy. ```toml theme={null} [http_service] internal_port = 8080 force_https = true ``` #### Example A web service with a Redis sidecar, both running in the same Machine. `fly deploy` builds the `web` service from a `Dockerfile` in your project directory; `redis` runs as a pre-built sidecar. **Dockerfile:** ```dockerfile theme={null} FROM nginxdemos/hello:plain-text ``` **compose.yml:** ```yaml theme={null} services: web: build: . ports: - "80:80" redis: image: redis:latest ``` **fly.toml:** ```toml theme={null} app = "my-compose-app" primary_region = "ord" [build.compose] [http_service] internal_port = 80 force_https = true auto_stop_machines = "stop" auto_start_machines = true [[vm]] size = "shared-cpu-1x" memory = "512mb" ``` Deploy with: ```bash theme={null} fly deploy ``` The `web` container handles inbound traffic on port 80, and the `redis` container runs alongside it as a sidecar. Your application code can reach Redis at `localhost:6379` because both containers share the same network namespace inside the Machine. > [!WARNING] > > **Warning:** Fly ignores `volumes:` declarations in your Compose file and prints `Warning: Could not read volume file...` at deploy time. The deploy still succeeds, but any data your containers write is stored on an ephemeral overlay that's wiped on restart or redeploy. For persistent storage, attach a [Fly Volume](/volumes) and mount it via `[mounts]` in `fly.toml`. #### Limitations * **Exactly one buildable service.** `fly deploy` requires exactly one service in the Compose file to specify `build`. All other services must use pre-built images. * **Secrets are global.** Secrets set with `fly secrets` are available to every container in the Machine. You can't scope a secret to a single service. * **Environment variable conflicts.** Fly injects runtime environment variables (like `FLY_APP_NAME`, `PRIMARY_REGION`, etc.) into all containers. These can overwrite values you define in your Compose file's `environment` block. ### Using the Machines API You can create multi-container Machines by sending a POST request to the Machines API. For example, using `curl`: ```bash theme={null} curl -X POST "https://api.machines.dev/v1/apps//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 theme={null} { "config": { "containers": [...], ... } } ``` The `config` object can also include other Machine fields like `guest` and `services`. Refer to the [Machines API documentation](/api/machines/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 theme={null} fly machine run --machine-config cli-config.json ``` The file should define `containers` at the root level, alongside any additional Machine configuration: ```json theme={null} { "containers": [...], ... } ``` You can also pass Machine configuration options from the command line: ```bash theme={null} 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] > > 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 theme={null} { "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 theme={null} 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 theme={null} { "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 theme={null} { "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 } ] } ] } ``` > [!NOTE] > > For more information about container configuration options, refer to the [Machines API documentation](/api/machines/models#fly-containerconfig). # Network Policies Source: https://docs.fly.io/machines/guides-examples/network-policies Restrict Fly Machine ingress and egress with network policies in the Machines API, selecting by app, Machine ID, or metadata with port allow rules. 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. > [!NOTE] > > 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 theme={null} 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 theme={null} { "all": true } ``` ### Specific Machine IDs To target individual Machines by their unique identifiers: ```json theme={null} { "machines": [ { "id": "abc" }, { "id": "def" } ] } ``` ### Metadata matching To select Machines based on their metadata attributes: ```json theme={null} { "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 theme={null} { "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 theme={null} POST /v1/apps//network_policies ``` Request body: ```json theme={null} { "name": "policy-name", "id": "optional-policy-id", "selector": { ... }, "rules": [ ... ] } ``` Include the `id` field to update an existing policy. ### List policies ```http theme={null} GET /v1/apps//network_policies/ ``` ### Delete a policy ```http theme={null} 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. # One App Per Customer - Why? Source: https://docs.fly.io/machines/guides-examples/one-app-per-user-why Why Fly.io recommends one app per customer: per-app secrets, load balancing, and scaling, plus isolated networks that can still talk via Flycast. 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](/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](/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](/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](/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. # Fly Machines Source: https://docs.fly.io/machines/index Start here for Fly Machines, fast-launching VMs on Fly.io. Find the Machines introduction, the REST API, and flyctl commands for running and updating Machines. ![Illustration by Annie Ruygt of a server with legs and arms, walking with a red bird hand-in-hand](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/fly-machines.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=a16299a4e1e13526a97ec4ec4afe6673) Fly 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. * [**Introduction to Fly Machines**:](/machines/overview) Learn whether you need low-level Machine control. Find out more about the lifecycle of Fly Machines and about scaling and placement. * [**Machines API:**](/machines/api) A simple and fast REST API for full control over our fast-launching Machines. * **[Run a New Machine](/machines/flyctl/fly-machine-run) or [Update a Machine](/machines/flyctl/fly-machine-update) with flyctl:** Configure, build, and start new Machines with a single command or update some or all of a Machine's configuration. On the other hand, try [Fly Launch](/reference/fly-launch) if you prefer easy app-wide configuration and containerized deployment for your app. # Machine states and lifecycle Source: https://docs.fly.io/machines/machine-states Fly Machine lifecycle states, from created, started, and suspended to replaced and migrated, plus version behavior and how to spot stuck Machines. 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 state lifecycle diagram](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/mermaid-diagram-1.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=611eebb8666af48ce7e113658fd267c0) ### Machine version state transitions ![Machine version state transitions diagram](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/mermaid-diagram-2.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=c51bc66bdc1b2c0928a3ae76a63465d1) ## 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](/machines/api) * [Working with Machines](/machines) * [Fly machine update command](/machines/flyctl/fly-machine-update) # An introduction to Fly Machines Source: https://docs.fly.io/machines/overview Get to know Fly Machines, the fast-launching VMs behind Fly.io: run one with fly machine run, follow the lifecycle, scale by clone or update, and pick regions. ![Illustration by Annie Ruygt of some birds launching a Fly rocket](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/fly-machines-intro.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=3c7d9e952d2bc072128741705aa4f003) Fly Machines are fast-launching VMs with a simple [REST API](/machines/api). They're [the compute](https://fly.io/blog/fly-machines/) behind the Fly.io platform. If you've launched an app on Fly.io, you're already using Fly Machines. We use the [Machines API](/machines/api) to build the orchestration for [Fly Launch](/apps). You can use it however you'd like. ## Do I need to control Machines directly? We give you low-level control over Fly Machines for when you need to be picky about how, where, and when to start VMs on Fly.io. For most applications, most of the time, you don't need to be picky! You scale things up to more cores or more memory, and out to more regions and more Machine counts. [Fly Launch](/apps) does all that for you with simple syntax and configuration. ## Concepts You can create and destroy Machines directly, and you have control over every Machine's lifecycle, resources, and region placement. ### Machines A Machine is the configuration and state for a single VM running on Fly.io. Every Machine will belong to a Fly App; Apps can have more than one Machine. You can start a Machine right now, without configuring anything: ```bash cmd theme={null} fly machine run --shell ``` ```output output theme={null} ? Select Organization: (personal) Searching for image 'ubuntu' remotely... Image size: 30 MB Success! A machine has been successfully launched in app flyctl-interactive-shells-g1b7jg6wpdbq0i8b8yrl4q9rlqu5pm-502673 Machine ID: d8d9510a295118 Connecting to fdaa:1:18:a7b:191:1aa9:a2a5:2... complete root@d8d9510a295118:/# uname -a Linux d8d9510a295118 5.15.98-fly #gf0950f1d7c SMP Sun Sep 17 02:33:12 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux ``` Go ahead and try it! When you `run` a Machine with `--shell`, we'll make a small one, and we'll tear it down when you're done poking around. ### Machine state The big Fly Machine trick is: starting up super fast; like, "in response to an HTTP request from a user" fast. Doing things like that is why you'd use Fly Machines directly, rather than [letting flyctl run your deployment](/launch/deploy). To get your head around that trick, start by understanding the lifecycle of a Fly Machine: 1. You create a Fly Machine with a [Create Machine request](/machines/api/machines-resource#create-a-machine), or with `fly machine run`. The Machine is in `created` state while we reserve space for your Machine, fetch your container from our global registry, and then build a root file system; this can take some time, maybe low double digit seconds. 2. Once the Fly Machine exists, we boot it up for you right away. The Machine is in `started` state. It's running. You can talk to it. This happens fast! Everything's already assembled; we're just booting. Usually this takes *well under a second*. The same goes for starting a stopped Machine with a [Start Machine request](/machines/api/machines-resource#start-a-machine), or with `fly machine start`. 3. You're done with the Fly Machine, so you stop it with a [Stop Machine request](/machines/api/machines-resource#stop-a-machine), or `fly machine stop`. The VM shuts down. The Machine is in `stopped` state. Its components are still assembled on our worker host, ready to start back up; if you want to do that, `GOTO 2`. 4. You're tired of the Fly Machine, and want it to go away permanently. Send a [Delete Machine request](/machines/api/machines-resource#delete-a-machine-permanently), or use `fly machine destroy`. We clear the resources we were holding for the Machine off our server. You can easily create and start a new Machine from the same image, but it'll be slower than stopping and starting an existing Machine. Learn more about [running a new Machine](/machines/flyctl/fly-machine-run) or [updating a Machine](/machines/flyctl/fly-machine-update) with flyctl commands. ### Scaling Machines > [!NOTE] > > Remember, the ordinary way to scale an application on Fly.io is to use [Fly Launch](/apps), which offers convenient > commands to scale instances out or up. Here, we're going to scale Machines directly, in a fiddly way. > > Whether or not you use `fly launch` to boot up a Fly App, every Machine belongs to an "App" (an "App" is ultimately just a named > collection of resources, configuration, and routing rules). Scale a workload out horizontally with `fly machine clone`: ```bash cmd theme={null} fly machine clone -a my-app --region ord 7811373c095228 ``` ```output output theme={null} Cloning machine 7811373c095228 into region ord Provisioning a new machine with image registry-1.docker.io/my-app/sleep:latest@sha256:597c3e Machine 28749edf4047d8 has been created... Waiting for machine 28749edf4047d8 to start... Machine has been successfully cloned! ``` You can clone a specific Machine as many times as you like, into specific regions; you can manage each of those Machines individually with `fly machine stop` and `fly machine start`. Scale a Machine up vertically with `fly machine update`: ```bash cmd theme={null} fly machine update -a my-app --vm-memory 512M 7811373c095228 ``` ```output output theme={null} Configuration changes to be applied to machine: 7811373c095228 (twilight-field-6033) ... // 12 identical lines "cpu_kind": "shared", "cpus": 1, - "memory_mb": 256 + "memory_mb": 512 }, "dns": {} } ? Apply changes? Yes Updating machine 7811373c095228 No health checks found Machine 7811373c095228 updated successfully! ``` Updating a Machine takes it down (like with `fly machine stop`), applies configuration changes, and brings it back up. If you're not changing the image, so we don't have to go fetch it from the global registry, this is fast, for the same reason `stop` and `start` are; we've already done the heavy lifting. ### Static Machine IPs > [!NOTE] > > **App-scoped static egress IPs are recommended.** The machine-scoped static egress IPs described below are a legacy feature. For most use cases, you should allocate [app-scoped static egress IPs](/networking/egress-ips#static-egress-ips-app-scoped) instead. Unlike machine-scoped IPs, app-scoped IPs can be shared between multiple machines in a region and are not deleted when machines are recreated. A machine-scoped static egress IP is attached to a single machine and isn't shared with other machines. It survives a [machine migration](/reference/machine-migration). 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](/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 ` ```bash cmd theme={null} fly machine egress-ip allocate e784e7d9a65208 ``` ```output output theme={null} ? Looks like you're allocating a static egress (outgoing) IP. This is an advanced feature, and is not needed by most apps. Are you sure this is what you want? Yes Allocated egress IPs for machine e784e7d9a65208: IPv4: 209.71.93.224 IPv6: 2a09:8280:e600::2e:951:0 ``` ### Placement When you `create`, `run`, or `clone` a Machine, you can pick a Fly.io region to place it in. Our API will contact the Machines API server (we call it `flaps`, but you don't have to care), which will in turn reach out to all the worker servers we have in the region, find out which ones have the requisite resources to host the Machine, and try to make a smart choice about which of those servers to 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. Read more about Machine placement and regional capacity in [this guide](/machines/guides-examples/machine-placement). > [!WARNING] > > **Placement can fail!** It shouldn't happen often, but we can run out of capacity in particular regions. Both flyctl and the Fly Machines > API are best-effort. If you're working with us at this level of control, it's on you to retry requests and ensure they go through. In exchange for that burden, we try to make sure the Fly Machines API is responsive, so you get quick answers. ## Recapping Fly Machine features * Manage with the Machines API or flyctl commands * Stop automatically when a program exits * Stop or start quickly, either manually or automatically based on traffic * 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](/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](/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](/flyctl/cmd/fly_machine) Use this command‑line reference for managing Machines via `flyctl` (create, update, stop, clone, etc). * [Working with the Machines API](/machines/api/working-with-machines-api) Read a practical guide to creating, updating, and managing Machines via API calls. * [Machines API reference](/api/machines) Use this low-level reference for the Machines API runtime, generated from the OpenAPI specification. # The Machine Runtime Environment Source: https://docs.fly.io/machines/runtime-environment See the environment variables Fly.io sets in a Machine, like FLY_APP_NAME, FLY_REGION, FLY_MACHINE_ID, FLY_PRIVATE_IP, and FLY_IMAGE_REF, and what each holds. Environment variables make several kinds of information available within a Machine's runtime environment. The values come from three sources: * [Secrets](/apps/secrets) * [Environment variable settings](/reference/configuration#the-env-variables-section) from the Machine config * Fly.io infrastructure, as detailed below ## *Environment variables* ### `FLY_APP_NAME` **App name**: Each app running on Fly.io has a unique app name. This identifies the app for the user, and can also identify its Machines on their IPv6 private network, using internal DNS. For example, `syd.$FLY_APP_NAME.internal` can refer to the app's Machines running in the `syd` region. Read more about [6PN Naming](/networking/private-networking#fly-io-internal-dns) in the [Private Networking](/networking/private-networking) docs. ### `FLY_MACHINE_ID` **Machine ID**: The Machine's unique ID on the Fly.io platform. This is the ID you use to target the Machine using flyctl and the Machines API. You can also see it in the [log](/flyctl/cmd/fly_logs) messages emitted by the Machine. ### `FLY_ALLOC_ID` **Allocation ID**: Same as the `FLY_MACHINE_ID`. ### `FLY_REGION` **Region name**: The three-letter name of the region the Machine is running in. Details of current regions are listed in the [Regions](/reference/regions) page. As an example, "ams" is the region name for Amsterdam. Not to be confused with the [HTTP header](/networking/request-headers#fly-region) `Fly-Region`, which is where the connection was accepted from. ### `FLY_PUBLIC_IP` **IPv6 public IP**: The full public outbound IPv6 address for this Machine. Read more in the [Network Services](/networking/services#outbound-ip-addresses) section. ### `FLY_IMAGE_REF` **Docker image reference**: The name of the Docker image used to create the Machine. `registry.fly.io/my-app-name:deployment-01H9RK9EYO9PGNBYAKGXSHV0PH` is an example of the Docker Image Reference's format. Useful if your app needs to launch Machine instances of itself to scale background workers to zero and back, as in [Rails Background Jobs with Fly Machines](https://fly.io/ruby-dispatch/rails-background-jobs-with-fly-machines/). ### `FLY_MACHINE_VERSION` **Machine configuration version**: A version identifier associated with a specific Machine configuration. When you update a Machine's configuration (including when you update its Docker image), it gets a new `FLY_MACHINE_VERSION`. Changing the Machine's metadata using the metadata endpoint of the Machines API doesn't trigger a new version. You can also find this value under the name `Instance ID` in the output of `fly machine status`. ### `FLY_PRIVATE_IP` **Private IPv6 address**: The IPv6 address of the Machine on its [6PN private network](/networking/private-networking). ### `FLY_PROCESS_GROUP` **Process group**: The Fly Launch [process group](/launch/processes) associated with the Machine, if any. ### `FLY_VM_MEMORY_MB` **Machine memory**: The memory allocated to the Machine, in MB. It's the same value shown on the Machine's page in the dashboard, and as VM Memory in the output of `fly machine status`. Learn more about [Machine sizing](/machines/guides-examples/machine-sizing). ### `PRIMARY_REGION` **Primary region**: This is set in your `fly.toml` or with the `--region` flag when using `fly launch`. Learn more about [configuring the primary region](/reference/configuration#primary-region). # Access Control Source: https://docs.fly.io/model-context-protocol/access-control Securing an MCP server on Fly.io starts with HTTP Authorization, since most stdio MCP servers were never meant to be exposed to the internet. 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: # Wireguard tunnels and Flycast Source: https://docs.fly.io/model-context-protocol/access-control/flycast Keep an MCP server off the public internet on Fly.io with a private IPv6 address and Flycast, then reach it with fly mcp proxy using .internal or .flycast URLs. 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](/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`](/flyctl/cmd/fly_proxy) to create a tunnel, or you can follow our blueprint to [Jack into your private network with WireGuard](/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](/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](/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. # HTTP Authorization Source: https://docs.fly.io/model-context-protocol/access-control/http-authorization Protect an MCP server on Fly.io with basic or bearer HTTP authorization: set FLY_MCP secrets on the app and pass matching flags to fly mcp proxy. 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](/flyctl/cmd/fly_mcp_wrap) and [fly mcp wrap](/flyctl/cmd/fly_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 theme={null} fly secrets set FLY_MCP_USER=Admin FLY_MCP_PASSWORD=S3cr3t ``` To use bearer authentication, set one secret in your application. For example: ```sh theme={null} 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 theme={null} openssl rand -base64 18 ``` And then on the client side pass the same values to the proxy as flags: For basic: ```json theme={null} { "mcpServers": { "filesystem": { "command": "/Users/rubys/.fly/bin/flyctl", "args": [ "mcp", "proxy", "--url=https://mcp.fly.dev/", "--user", "Admin", "--password", "S3cr3t" ] } } } ``` For bearer: ```json theme={null} { "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. # Deploy on Source: https://docs.fly.io/model-context-protocol/deploy-on Why to run each MCP server on its own Fly Machine instead of your laptop: isolation, plus access to volumes, SQLite databases, and app internal state. 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. # A Fly Machine Source: https://docs.fly.io/model-context-protocol/deploy-on/fly-machine Run a remote MCP server on a Fly Machine: launch the flyio/mcp filesystem image on Fly.io, test it in the MCP inspector, and connect Claude Desktop. [Fly Machines](/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](/flyctl/install), and have successfully run either [`fly auth signup`](/flyctl/cmd/fly_auth_signup) or [`fly auth login`](/flyctl/cmd/fly_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 theme={null} FROM flyio/mcp VOLUME /data CMD [ "npx", "-f", "@modelcontextprotocol/server-filesystem", "/data/" ] ``` Now use the [fly launch](/flyctl/cmd/fly_launch) command: ```sh theme={null} 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 > [!NOTE] > > 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 theme={null} fly mcp proxy -i ``` Navigate to [http://127.0.0.1:6274](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 theme={null} { "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. # Your application Source: https://docs.fly.io/model-context-protocol/deploy-on/your-app Run an MCP server inside your own Phoenix or Rails app on Fly.io with Tidewave, then point Cursor or another MCP client at it through mcp.json. [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 theme={null} # 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 theme={null} gem "tidewave", group: :development ``` Example `.cursor/mcp.json`: ```json theme={null} { "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). # Deploy with Source: https://docs.fly.io/model-context-protocol/deploy-with Compare fly launch, fly machine run, and the Machines API for deploying a stdio MCP server, using the Filesystem MCP Server with a volume as the example. [YouTube video](https://www.youtube.com/embed/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. # Deploy an MCP Server with fly launch Source: https://docs.fly.io/model-context-protocol/deploy-with/fly-launch Deploy an MCP server with fly launch on Fly.io from a short Dockerfile, use --flycast for private access, and redeploy changes to fly.toml with fly deploy. [Fly launch](/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](/flyctl/install), and have successfully run either [`fly auth signup`](/flyctl/cmd/fly_auth_signup) or [`fly auth login`](/flyctl/cmd/fly_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 theme={null} FROM flyio/mcp VOLUME /data CMD [ "npx", "-f", "@modelcontextprotocol/server-filesystem", "/data/" ] ``` Now use the [fly launch](/flyctl/cmd/fly_launch) command: ```sh theme={null} 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`](/reference/configuration). If you make any change to your `Dockerfile` or `fly.toml`, run [`fly deploy`](/flyctl/cmd/fly_deploy) to apply the changes. ## Accessing the MCP via an inspector > [!NOTE] > > 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 theme={null} fly mcp proxy -i ``` Navigate to [http://127.0.0.1:6274](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 theme={null} { "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. # fly machines run Source: https://docs.fly.io/model-context-protocol/deploy-with/flyctl-machines Deploy an MCP server with fly machine run on Fly.io: create the app, allocate IPs, add a volume, and start a Machine that uses fly mcp wrap as its entrypoint. The [Fly.io command line interface](/flyctl/cmd/fly_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](/flyctl/install), and have successfully run either [`fly auth signup`](/flyctl/cmd/fly_auth_signup) or [`fly auth login`](/flyctl/cmd/fly_auth_login). The first step is intended to be run in an empty directory. ## Create your app Now use the [fly apps create](/flyctl/cmd/fly_apps_create) command: ```sh theme={null} 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` parameter, 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 theme={null} 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 theme={null} 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 theme={null} fly volumes create data --region iad --yes ``` Adjust the [region](/reference/regions#fly-io-regions) as necessary. ## Create a machine This next part contains a lot of [flags](/flyctl/cmd/fly_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 theme={null} 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`](/flyctl/cmd/fly_machine_create) instead. Once this command completes, you can update your `fly.toml` to include this new information using the following command: ```sh theme={null} fly config save --yes ``` ## Accessing the MCP via an inspector > [!NOTE] > > 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 theme={null} fly mcp proxy -i ``` Navigate to [http://127.0.0.1:6274](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 theme={null} { "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. # Deploy an MCP Server with the Machines API Source: https://docs.fly.io/model-context-protocol/deploy-with/machines-api Deploy an MCP server with the Machines API on Fly.io using curl and an org token to create the app, IP addresses, volume and Machine through REST calls. The [Machines API](/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](/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 theme={null} 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](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: ![Obtaining an organization token via the dashboard](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/orgtoken.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=874b5b36307aa516034cd9d3ed048b29) Click on Copy to clipboard, and then run the following command with your token pasted inside the quotes: ```sh theme={null} 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](/machines/api/working-with-machines-api#api-addresses). If you are not sure, use the public base URL: ```sh theme={null} 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 theme={null} 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](/machines/api/apps-resource#create-a-fly-app) API: ```json theme={null} 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 > [!NOTE] > > 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`](/flyctl/cmd/fly_ips) command instead. The following will create a shared IPv4 address and a dedicated IPv6 address: ```json theme={null} curl -i -X POST \ -H "Authorization: Bearer ${FLY_API_TOKEN}" \ -H "Content-Type: application/json" \ "https://api.fly.io/graphql" \ -d @- < [!NOTE] > > 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 theme={null} fly mcp server -i ``` Navigate to [http://127.0.0.1:6274](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 > [!WARNING] > > **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](/security/tokens) for information on how to obtain a token. # Model Context Protocol Source: https://docs.fly.io/model-context-protocol/index Run Model Context Protocol servers on Fly.io: fly mcp launch for stdio servers, plus choosing a transport, deploy method, host, and access control. [YouTube video](https://www.youtube.com/embed/74c1ByGvFPE) > [!NOTE] > > 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](/flyctl/install), and have successfully run either [`fly auth signup`](/flyctl/cmd/fly_auth_signup) or [`fly auth login`](/flyctl/cmd/fly_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). # Launch an MCP Server with fly mcp launch Source: https://docs.fly.io/model-context-protocol/launch fly mcp launch deploys an npx, uvx, go run, or Docker stdio MCP server to a Fly Machine and configures your client; fly mcp inspect and destroy follow. 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 theme={null} 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](/model-context-protocol/examples) for a number of examples. See the [`fly mcp launch` documentation](/flyctl/cmd/fly_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: > [!NOTE] > > 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 theme={null} 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 theme={null} fly mcp destroy --claude --server time ``` This will also remove the configuration entry from the MCP client. # MCP Transports Source: https://docs.fly.io/model-context-protocol/transports MCP transports on Fly.io: stdio (recommended), Streamable HTTP, and deprecated SSE, demonstrated with Anthropic's Everything MCP Server and printEnv. 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. # SSE Source: https://docs.fly.io/model-context-protocol/transports/sse Deploy an SSE MCP server on Fly.io as is: expose its port, disable smoke checks in fly launch, and connect the MCP inspector or Cursor to the /sse URL. 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 theme={null} git clone https://github.com/modelcontextprotocol/servers.git cd servers cp src/everything/Dockerfile . ``` Make the following changes to the `Dockerfile`: ```diff theme={null} RUN npm ci --ignore-scripts --omit-dev + +EXPOSE 3001 -CMD ["node", "dist/index.js"] +CMD ["node", "dist/sse.js"] ``` Now run: ```sh theme={null} fly launch --ha=false --smoke-checks=false ``` Access the MCP server via the MCP inspector: ```sh theme={null} 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 theme={null} { "mcpServers": { "everything": { "url": "https://appname.fly.dev/sse", "env": {} } } } ``` With SSE transports, [`fly mcp proxy`](/flyctl/cmd/fly_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](/flyctl/cmd/fly_proxy) to access your MCP server via an [`.internal`](/networking/private-networking) or [`.proxy`](/networking/flycast) address. * You need to [route the request to a specific machine](/networking/dynamic-request-routing#the-fly-force-instance-id-header). # stdio Source: https://docs.fly.io/model-context-protocol/transports/stdio Run a stdio MCP server remotely on Fly.io by wrapping it with fly mcp wrap in the Dockerfile and connecting from Claude Desktop through fly mcp proxy. stdio MCP servers are not intended to be run remotely, but [`fly mcp`](/flyctl/cmd/fly_mcp) provides the ability to proxy and wrap them. The data flow is that 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: ![MCP Proxy/Wrapper data flow](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/mcp-proxy-wrap-flog.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=90eef2fd90b2b21bb7a1277fcd7d2d6a) Start by cloning the MCP servers git repository and making a copy of the `Dockerfile`: ```sh theme={null} git clone https://github.com/modelcontextprotocol/servers.git cd servers cp src/everything/Dockerfile . ``` Make the following changes to the `Dockerfile`: ```diff theme={null} 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 theme={null} fly launch --ha=false ``` Access the MCP server via the MCP inspector: ```sh theme={null} fly mcp proxy -i ``` An example `claude_desktop_config.json`: ```json theme={null} { "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. # Streaming HTTP Source: https://docs.fly.io/model-context-protocol/transports/streaming-http Deploy a Streamable HTTP MCP server on Fly.io unchanged: expose port 3001, launch without smoke checks, and connect the MCP inspector or Cursor to the /mcp URL. 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 theme={null} git clone https://github.com/modelcontextprotocol/servers.git cd servers cp src/everything/Dockerfile . ``` Make the following changes to the `Dockerfile`: ```diff theme={null} RUN npm ci --ignore-scripts --omit-dev + +EXPOSE 3001 -CMD ["node", "dist/index.js"] +CMD ["node", "dist/streamableHttp.js"] ``` Now run: ```sh theme={null} fly launch --ha=false --smoke-checks=false ``` Access the MCP server via the MCP inspector: ```sh theme={null} 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 theme={null} { "mcpServers": { "everything": { "url": "https://appname.fly.dev/mcp", "env": {} } } } ``` With Streaming HTTP transports, [`fly mcp proxy`](/flyctl/cmd/fly_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](/flyctl/cmd/fly_proxy) to access your MCP server via an [`.internal`](/networking/private-networking) or [`.proxy`](/networking/flycast) address. * You need to [route the request to a specific machine](/networking/dynamic-request-routing#the-fly-force-instance-id-header). # Error codes and troubleshooting Source: https://docs.fly.io/monitoring/error-codes Look up Fly Proxy error codes like PC01, PT05, PM03, PR01, and PA03 from your app logs, grouped by connection, TLS, Machine, and routing. The Fly.io platform logs errors to customer log streams. Each log line contains an error code and a field containing that code. This page gives more context about the errors and how to troubleshoot them. ## Proxy errors The Fly proxy issues different categories of errors, identifiable by the second letter in each code, referring to: Upstream, Connection, TLS, Machine, App, Edge and Routing errors. ### Internal Errors These errors are internal to the Fly proxy. They're not related to your application behavior. #### PP01: Failed to set TCP socket options The proxy wasn't able to set TCP socket options. This is an internal Fly.io error. ### Upstream Proxy errors Upstream errors occur when the proxy fails to send or complete a request to one of your upstream application machines. #### PU01: Failed HTTP/2 handshake In the case that your app accepts h2c requests, `PU01` errors indicate that the HTTP/2 handshake to one of your machines failed. This error is not relevant to applications accepting only HTTP/1 connections. #### PU02: Failed to complete HTTP request to instance An HTTP request to a machine was started, but could not be completed. #### PU03: Unreachable worker host The underlying host where a machine lives became unreachable. This is an internal Fly.io error unrelated to your application. #### PU04: Could not build HTTP request It wasn't possible to create an HTTP request, for unknown reasons. #### PC01: Machine refused connection on port A request was sent to a specific port that isn't open on the target machine. Is your app listening on the correct port? Is your app listening on `0.0.0.0` or `::`? Make sure it's not listening on `127.0.0.1`. Check your app startup logs. Servers often print the address they're listening on. #### PC02: Connection refused A request was sent to an unspecified port that isn't open on the target machine. Is your app listening on `0.0.0.0` or `::`? Make sure it's not listening on `127.0.0.1`. Check your app startup logs. Servers often print the address they're listening on. #### PC03: Connection reset This indicates a problem with your application resetting a TCP connection prematurely. Check your application logs for possible causes. #### PC04: Connection aborted This indicates a problem with your application aborting a TCP connection prematurely. Check your application logs for possible causes. #### PC05: Connection timed out This indicates a problem with connections timing out to your application. Check the following to diagnose: * Application logs may indicate the cause of a timeout * Ensure your app isn't overloaded by properly set its [concurrency limits](/apps/concurrency) * Check [application metrics](/monitoring/metrics#managed-grafana) for signs of resource exhaustion (CPU, memory, disk I/O) #### PC06: Unidentified I/O error A connection to a machine experienced an unidentifiable I/O error. #### PC07: Connection retries exhausted The proxy failed to connect to a machine after too many retries. #### PC08: TCP timeout couldn't be set Failed to set the TCP timeout for an upstream connection. This is an internal Fly.io issue. ### TLS errors TLS errors are related to the automatic TLS termination provided by Fly Proxy. These errors occur before a request is passed on to your application. #### PT01: Exceeded TLS handshake rate limit A specific IP range exceeded the rate limit for TLS handshake attempts. #### PT02: Exceeded rate limit for SNI TLS handshake requests to a specific SNI exceeded the rate limit. #### PT03: TLS handshake canceled A TLS handshake was canceled prematurely. #### PT04: TLS handshake failed A TLS handshake failed. #### PT05: No valid TLS certificate for SNI No TLS certificate was found for a specific server name, and the connection was aborted. #### PT06: No valid TLS certificate No TLS certificate was found for the connection, and the connection was aborted. #### PT07: TLS handshake I/O error A TLS handshake failed due to an unspecified I/O error. #### PT08: TLS handshake timed out A TLS handshake timed out. #### PT09: Internal TLS error An internal TLS error occurred. ### Machine-related errors This class of errors relates to how the proxy behaves when starting and stopping machines on request. #### PM01: Machines API error The Machines API returned an error. If the underlying error is known, it's displayed. #### PM02: Machine wake internal error An internal Fly.io error prevented a machine transitioning from a `stopped` to `started` state. #### PM03: Machine wake timeout The API request to transition a machine from a `stopped` to `started` state timed out. #### PM04: Machine wake parsing error A request parsing error prevented a machine transitioning from a `stopped` to `started` state. #### PM05: Machine connection failed The proxy failed to connect to a machine. If the underlying error is known, it's displayed. #### PM06: Missing app name The Machines API requires an app name to operate on an individual machine. That app name wasn't specified. #### PM07: Machine state change failed The proxy wasn't able to stop or start a machine. If the underlying error is known, it's displayed. #### PM08: Non-startable machine state A machine could not be started since due to its current non-startable state, such as `stopping` or `destroyed`. The state is displayed along with this message. #### PM09: Unknown machine state The proxy doesn't recognize the machine's current state. #### PM10: Machine start canceled A machine start was canceled prematurely due to an internal Fly.io problem. #### PM11: Machine recently stopped Machine states are broadcast to the Fly proxies in an eventually consistent manner. So, edge proxies may not have an up-to-date picture about machine states. A machine that appears as `started` to the edge proxy may actually have been recently stopped. If the edge proxy forwards a request to a recently stopped machine, and there are other machines available to handle the request, the `PM11` error will be returned to the edge proxy. The error informs the proxy about the `stopped` state of the machine, and instructs the edge to forward the request to another machine. If the edge proxy believes there's only one machine that can service the request, this logic is bypassed. The request is forwarded to the machine even if it was stopped recently. The current threshold for 'recently stopped' is 5 minutes. #### PA01: Replay/retry buffer exceeded To prepare for the possibility of receiving [`fly-replay` response header](/networking/dynamic-request-routing#how-fly-replay-works), or for retrying failed requests, Fly edge proxies buffer requests up to 10MB. If a request grows larger than 10MB, buffering stops, making it impossible to replay or retry the request. When this happens, `PA01` is emitted. #### PA02: Excess fly-replay headers After a ['fly-replay' response header](/networking/dynamic-request-routing#how-fly-replay-works) 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: Invalid fly-replay Your app returned an invalid ['fly-replay' response](/networking/dynamic-request-routing#how-fly-replay-works). #### PA04: Replay target app not found Your app returned a ['fly-replay' response header](/networking/dynamic-request-routing#how-fly-replay-works) targeting a non-existent application in the `app` parameter. #### PA05: Unauthorized replay target app Your app returned a ['fly-replay' response header](/networking/dynamic-request-routing#how-fly-replay-works) targeting an application belonging to a different Fly.io organization. Only apps in the same organization may replay requests to each other. ### Edge proxy errors This category of errors refers to internal Fly.io errors happening only on edge proxies. #### PE01: Replay source app not found An internal error occurred while using `fly-replay` related to the source app name. #### PE02: Replay source organization not found An internal error occurred while using `fly-replay` related to the source organization. ### Routing errors This category refers to request routing errors between proxies and applications. #### PR01: No healthy machines No healthy machines were found to forward a request to. This error is most common in non-HTTP TCP services. The reasons could be: **Exhausting restart retries due to boot errors** Your app machines may all be stopped due to boot errors exhausting the number of restart retries. Check your app logs and `fly status`. **Deployments with volumes are failing** If your app uses volumes and your rolling deployment is failing, you might encounter this error. Check your app logs and `fly status`. **Using the `immediate` deploy strategy** If you use the `immediate` deploy strategy, all current machines will be replaced at once, possibly leading to downtime and some `PR01` errors. **App concurrency limits reached** [Concurrency limits](/apps/concurrency) set in `fly.toml` define how traffic should be balanced across machines in your app. To diagnose, check if: * your app is using too much CPU, memory or disk I/O * your app applies its own concurrency limits * Connection pools to external services like databases are exhausted * Connections to external services from your app are slow #### PR02: Machine not found The Fly proxy couldn't find a specific machine ID after the request was forwarded from an edge proxy. The VM was likely shut down between when the proxy received the request and when it got forwarded. This error is most common during [`bluegreen` deployments](/reference/configuration#picking-a-deployment-strategy). #### PR03: No candidate machines found after retries This error is functionally similar to `PR01`. It only applies to HTTP services however, and up to 90 retries are attempted before the proxy gives up and issues this error. This error should also display the cause of the most recent error before this one. #### PR04: No candidate machines found after retries This error is functionally similar to `PR03`, except it will not display previous errors. #### PR05: Statics retrieval failed The proxy failed to retrieve a static file from the specified Tigris storage bucket. #### PL01: Bypassed connection concurrency limit [Concurrency limits](/apps/concurrency) set in `fly.toml` define how traffic should be balanced across machines in your app. This error occurs when concurrency is measured as the number of concurrent **connections**. To diagnose, check if: * your app is using too much CPU, memory or disk I/O * your app applies its own concurrency limits * Connection pools to external services like databases are exhausted * Connections to external services from your app are slow #### PL02: Bypassed request concurrency limit This error is similar to `PL01`, but refers to concurrency measured as the number of concurrent **requests**. # Export logs Source: https://docs.fly.io/monitoring/exporting-logs Export logs from Fly.io apps to Datadog, S3, Logtail and other services with the Fly Log Shipper, a Vector-based app you can filter by app or region. The Fly Log Shipper app enables you to aggregate your app's logs to a service of your choice. Your app's output to `stdout` become logs in Fly.io. Live log tail and log search are good enough for most things. But if you need to export your logs to an external service, such as Datadog or AWS S3, then you can use the Log Shipper. ## The Fly Log Shipper Here's the easiest way to ship your logs to a location of your choosing. The [Fly Log Shipper app](https://github.com/superfly/fly-log-shipper) hooks into Fly.io's internal log stream. You can run this in your Fly.io organization like any other app. The Fly Log Shipper runs [Vector](https://vector.dev/), which grabs the logs and sends them to a location of your choosing (via a Vector [sink](https://vector.dev/docs/reference/configuration/sinks/)). You can select one or more items from the list of supported [Providers (sinks)](https://github.com/superfly/fly-log-shipper#provider-configuration) and configure the Fly Log Shipper to run those sinks. Each provider just needs some environment variables (or secrets) set for them to work. ## Using the Fly Log Shipper Here's an example. To ship logs to [Logtail](https://betterstack.com/logtail), you would do the following: ```bash theme={null} # Make a directory for your log shipper app mkdir logshippper cd logshippper # Create the app but don't deploy just yet fly launch --no-deploy --image ghcr.io/superfly/fly-log-shipper:latest # Set some secrets. The secret / env var you set # determines which "sinks" are configured ORG=personal fly secrets set ORG=$ORG fly secrets set ACCESS_TOKEN=$(fly tokens create readonly $ORG) fly secrets set LOGTAIL_TOKEN= ``` You can configure as many providers as you'd like by adding more secrets. The secrets needed are determined by [which provider(s)](https://github.com/superfly/fly-log-shipper#provider-configuration) you want to use. The Log Shipper pulls logs from Fly.io's internal log stream and doesn't need to accept inbound connections, so it shouldn't have any public services or IP addresses. Before deploying, edit the generated `fly.toml` file and delete the entire `[http_service]` (or `[[services]]`) section. Then deploy without allocating a public IP address: ```bash theme={null} fly deploy --no-public-ips ``` If you already deployed a Log Shipper with a public IP address, list its addresses with `fly ips list` and remove them with `fly ips release
`. ## Add a health check The Log Shipper can occasionally stop shipping logs without exiting, so it's a good idea to add a health check. Vector serves a `/health` endpoint on port 8686. Since the app has no services, use a top-level [`[checks]` section](/reference/configuration#the-checks-section) in `fly.toml`, which doesn't require a public service or IP address: ```toml theme={null} [checks] [checks.vector] port = 8686 type = "http" method = "get" path = "/health" grace_period = "10s" interval = "30s" timeout = "5s" ``` Check the health status with `fly checks list`. ## Shipping specific logs By default, the log shipper gets logs from every app running within your organization (organization is set by the `ORG` secret/environment variable). To narrow that down, you can set a [`SUBJECT`](https://github.com/superfly/fly-log-shipper#subject) environment variable in your instance of the Fly Log Shipper. That can be set as a secret, or as an environment variable in your `fly.toml` file. Subjects are in the format `logs...`. You can set the Log Shipper to narrow down to a specific instance, a specific region, and/or a specific application. There are [2 wildcards](https://docs.nats.io/nats-concepts/subjects#wildcards) you can use: * `*` wildcards go between strings and can be used multiple times * `>` wildcards go at the end of a string and can be used once For example, to only ship logs for an application named `sandwich`, you would set the `SUBJECT` environment variable like so (in your Log Shipper `fly.toml` file): ```toml theme={null} [env] SUBJECT = "logs.sandwich.>" ``` This uses wildcard `>` to say to grab all logs from application `sandwich` no matter what region or instance they came from. Another example: ```toml theme={null} [env] SUBJECT = "logs.*.dfw.>" ``` This `SUBJECT` says to grab logs from all instances of any applications hosted in the `dfw` region. ## Configuring Vector Based on your provider (or preferences), it may be necessary to customize the [Vector configuration](https://vector.dev/docs/reference/configuration/). This is done with a `vector.toml` configuration file and, thanks to [Machine files](/reference/configuration#the-files-section), it's as simple as copying the [source](https://github.com/superfly/fly-log-shipper/blob/3b780b3a3c68fdbbbb55430d7d9ff1eb377fdbf0/vector-configs/vector.toml) `vector.toml` to a local directory, modifying it according to your requirements, then saving it and redeploying: ```sh theme={null} fly deploy --file-local="/etc/vector/vector.toml=/path/to/local/vector.toml" ``` That's it! The baked in config file is overwritten and Vector will use your modified config. ## Internals Fly.io ships logs through a [NATS](https://nats.io) stream. This is available to all of your apps via `nats://[fdaa::3]:4223`, which is where the Log Shipper grabs the logs. The Vector configuration to grab those logs within the Log Shipper is in the [vector.toml file](https://github.com/superfly/fly-log-shipper/blob/main/vector-configs/vector.toml). You can contribute to (or, you know, fork) this repository to add providers (sinks) of your own! ## Avoid duplicate log messages in high availability apps If you want to run more than one Machine for high availability, the [NATS](https://docs.nats.io/) endpoint supports [subscription queues](https://docs.nats.io/nats-concepts/core-nats/queue) to ensure messages are only sent to one subscriber of the named queue. The `QUEUE` secret can be set to configure an arbitrary queue name in your Log Shipper `fly.toml` file if you want to run multiple log processes for HA and avoid duplicate messages being shipped. For example: ```toml theme={null} [env] QUEUE = "org-logs" ``` # Monitoring on Fly.io Source: https://docs.fly.io/monitoring/index Fly.io monitoring docs: managed Prometheus and Grafana metrics, Sentry error tracking, and guides to live tail, log search, the logs API, and export. ![Illustration by Annie Ruygt of a smirking ghost sitting infront of a laptop](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/spooky.webp?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=b2620eec3e25ad75f1c88a1e0a894e52) ## Metrics *Fully-managed metrics solutions to help you monitor your apps.* * **[Metrics](/monitoring/metrics):** Learn how to configure and use the Fly.io metrics for Prometheus and Grafana, and built-in and custom metrics for your apps. *** ## Error monitoring *Error monitoring for your apps.* * **[Sentry](/monitoring/sentry):** Use Sentry to get actionable insights to resolve the most important code-related issues in your app. *** ## Logging *Live tail and search your app’s logs, access them via API, or ship them where you want them.* * **[Logging overview](/monitoring/logging-overview):** How logs work on Fly.io. * **[Logs API options for programmatic access](/monitoring/logs-api-options)::** How to fetch, stream or export logs programmatically without touching the CLI. * **[Live tail logs](/monitoring/live-tail-logs):** Where to access live tail logs. * **[Search logs](/monitoring/search-logs):** Searchable app logs in Grafana (beta). * **[Export logs](/monitoring/exporting-logs):** Aggregate your logs to a service of your choice. * **[Error codes and troubleshooting](/monitoring/error-codes):** Look up error codes and get tips for troubleshooting. # Live tail logs Source: https://docs.fly.io/monitoring/live-tail-logs Tail Fly App logs live with fly logs, filter by Machine with --instance, turn on LOG_LEVEL=debug for flyctl, or use the dashboard Live Logs page. You can quickly live tail logs in your dashboard or using the `fly logs` command. ## Use flyctl to view logs in your terminal The `fly logs` command prints the most recent app logs and then tails the current logs in real-time. This is useful for watching logs during deployment or when running other commands. You can open a new terminal window and run this command in an app's source directory, or use the `--app` option to specify an app: ```bash theme={null} fly logs --app my-app-name ``` The logs continue to output until you close the terminal or stop it, for example with `ctrl + c`. You can filter the log output by Machine or region. For example, use the Machine ID with the `--instance` option to see logs for that Machine only: ```bash theme={null} fly logs --instance ``` See the `fly logs` help or [docs](/flyctl/cmd/fly_logs) for option details. ### Use flyctl to activate debug logging for a command `LOG_LEVEL=debug` prints all the logs into the console as a command runs. To activate debug logging for `fly deploy` run: ```bash theme={null} LOG_LEVEL=debug fly deploy ``` ## Use the dashboard to view logs To view recent and live tail app logs in the dashboard, go to the **Live Logs** page. From there, you can use the dropdowns to filter logs by region or instance (Machine). You can also view specific Machine logs. Go to **Machines**, click a Machine, and then click the Machine logs icon. # Logging overview Source: https://docs.fly.io/monitoring/logging-overview See how Fly.io app logs flow from stdout through Vector and NATS, then live tail with fly logs, use the Logs API, search logs in Grafana, or export them. Fly.io app logs are the stdout from the processes run in your apps: whatever an app outputs becomes a log. Your apps run in Machines. Inside each Machine, we inject a process (init) that runs and monitors your app. Since we build Machines from Docker images, init is taking ENTRYPOINT + CMD and running that. The init program is, among other things, gathering process output from stdout and redirects it to a socket. Outside of the Machine, on the host, we take that output and send it to [Vector](https://vector.dev/docs/reference/configuration/sinks/nats/) via yet another socket. Vector ships logs (your app’s output) to an internal NATS cluster. Clients can subscribe to specific topics, and NATS sends the requested data to those subscribers. A proxy sits in front of NATS, to ensure you only see your own logs, and you can hook into NATS via this proxy to get your logs. Vector can act as a NATS client, read the logs, and ship them somewhere. An example of this is our Fly Log Shipper app. ## Live tail logs You can live tail logs from the dashboard or with the `fly logs` command. For more information, see [Live tail logs](/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](/monitoring/logs-api-options). ## Search logs We have an app log search feature in Grafana that retains logs for 7 days. For more information, see [Search logs](/monitoring/search-logs). ## Export logs Try the Fly Log Shipper app to export logs to a service. For more information, see [Export logs](/monitoring/exporting-logs). ## Error codes and troubleshooting Look up error codes from logs and get tips on troubleshooting them. For more information, see [Error codes and troubleshooting](/monitoring/error-codes). # Logs API options for programmatic access Source: https://docs.fly.io/monitoring/logs-api-options Fetch Fly.io app logs in code through the HTTP API behind fly logs, a NATS subscription for real-time streams, or the Fly Log Shipper to an external sink. ![Illustration by Annie Ruygt of a rocket ship, a satellite dish, a cargo container, and a clock, all representing different options for logs](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/logs-api-options.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=92c2e5e2ca296bdaed2c9d58a0c977ae) ## Overview Fly.io apps run on Firecracker microVMs we call Machines. Each Machine captures `stdout`/`stderr`, ships those logs over [NATS](https://nats.io), and stores them for a period of time in a VictoriaLogs-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: * **HTTP API**: What `fly logs` uses under the hood. It isn't in the API reference, but its parameters are documented below. * **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: ``` The response is a JSON:API document with a `data` array of log entries and a `meta.next_token` cursor for paging. By default, the most recent 100 logs from the last 24 hours are returned. You can pass these query params to filter logs or set a starting timestamp: * `region`: three-letter region code. Returns only logs from this region. * `instance`: a Machine ID. Returns only logs from this instance. * `next_token`: a nanosecond Unix timestamp (e.g. `1779235200000000000`). Returns logs after this time, used either as a paging cursor or to backfill from a specific moment. Each response includes a `meta.next_token` that you can pass in your next request to page forward. * `next_token` replaces the previously documented `start_time` (ISO 8601) param. This endpoint isn’t officially supported for external use, but it’s mostly stable: `flyctl` depends on it. 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 7 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] > > **Note:** The NATS proxy is an internal transport without a versioned API or stability guarantee. It works, but may change as we evolve our logging infrastructure. If you’re building something serious, we recommend exporting logs via the log shipper 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 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](/blueprints/observability-for-user-apps#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 uses NATS internally. Since it's maintained by Fly, it's more likely to stay compatible with infrastructure changes than a custom NATS client. It's 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](/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. # Metrics on Fly.io Source: https://docs.fly.io/monitoring/metrics Monitor apps with managed Prometheus and Grafana on Fly.io: query built-in proxy, instance, volume and Postgres metrics, add alerts, and expose custom metrics. ![Illustration by Annie Ruygt of some Machines strutting along](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/monitor-machines.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=609f8fc35f27a91e85e0f5d9cb8d34aa) The Fly.io platform includes a fully-managed metrics solution to help you easily monitor your Fly apps. It includes the following components: * [**Prometheus on Fly.io**](#prometheus-on-fly-io): Managed Prometheus-compatible time series storage * [**Dashboards**](#dashboards): Managed Grafana with detailed visualizations of all built-in metrics * [**Built-in Metrics**](#built-in-metrics): Metrics automatically sent from every Fly app you deploy * [**Custom Metrics**](#custom-metrics): Expose additional metrics from Fly apps for further customization ## Prometheus on Fly.io [Prometheus](https://prometheus.io/) is a popular open source monitoring system used to store and query metrics efficiently, with a stable [HTTP querying API](https://prometheus.io/docs/prometheus/latest/querying/api/) compatible with a range of systems. **Prometheus on Fly.io** is a fully-managed service based on [VictoriaMetrics](https://victoriametrics.com/). It [supports](https://docs.victoriametrics.com/#prometheus-querying-api-usage) most common Prometheus querying API endpoints: * [`/api/v1/query`](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) * [`/api/v1/query_range`](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) * [`/api/v1/series`](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) * [`/api/v1/labels`](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names) * [`/api/v1/label//values`](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) * [`/api/v1/status/tsdb`](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats) * [`/federate`](https://prometheus.io/docs/prometheus/latest/federation/) Note that [remote read](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read) (`/api/v1/read`) [remote storage integration](https://prometheus.io/docs/prometheus/latest/storage/#remote-storage-integrations) is [not supported](https://docs.victoriametrics.com/FAQ.html#why-doesnt-victoriametrics-support-the-prometheus-remote-read-api). ### MetricsQL Prometheus queries are typically based on the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. Prometheus on Fly.io queries use VictoriaMetrics [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html), a backwards-compatible query language that fixes user experience issues and adds useful features and functions on top of PromQL. Key features: * [Better `rate()` and `increase()`](https://medium.com/@romanhavronenko/victoriametrics-promql-compliance-d4318203f51e#cade) functions that just work. No need for [`irate` workarounds](https://www.percona.com/blog/2020/02/28/better-prometheus-rate-function-with-victoriametrics/) or appending Grafana's [magical `$__rate_interval`](https://grafana.com/blog/2020/09/28/new-in-grafana-7.2-__rate_interval-for-prometheus-rate-queries-that-just-work/) selector to every query. In fact, you can even omit the square brackets entirely and MetricsQL will do the right thing. * Many more [label manipulation functions](https://docs.victoriametrics.com/MetricsQL.html#label-manipulation-functions) such as `drop_common_labels`, `label_set`, etc. * [`topk_avg`](https://docs.victoriametrics.com/MetricsQL.html#topk_avg), which returns the top `k` time series averaged across the entire series range (not just individual points), plus the sum of all remaining series in an "other" label. Useful for giving a small, filtered view across a potentially large number of series. ### Querying Queries can be sent to the following endpoint: ``` https://api.fly.io/prometheus// ``` You’ll need to authenticate with a [Fly Access Token](/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 **Find your Organization slug** List your organizations, find the org slug and set it as a local variable. ```bash theme={null} flyctl orgs list ORG_SLUG=[org-slug] ``` **Get an access token** ```bash theme={null} TOKEN=$(flyctl auth token) ``` **Test it out!** ```shell theme={null} curl https://api.fly.io/prometheus/$ORG_SLUG/api/v1/query \ --data-urlencode 'query=sum(increase(fly_edge_http_responses_count)) by (app, status)' \ -H "Authorization: Bearer $TOKEN" ``` ## Retention, cost, and limits Prometheus on Fly.io retains metric data for approximately 15 days and is intended for operational monitoring. If you need longer retention, run your own monitoring stack. Fly.io's [Observability for User Apps](/blueprints/observability-for-user-apps) guide deploys a ready-made stack, or you can federate from the Fly.io endpoint into your own Prometheus installation. There's currently no additional charge for the managed Prometheus and Grafana. Pricing could change in the future, and Fly.io will give advance notice. A couple of practical limits to be aware of: * We cap your app's metrics endpoint response at 16 MB and drop anything larger. * We may drop very high-cardinality custom metrics. ## Dashboards For more advanced metrics monitoring, you can use dashboards to organize and visualize complex Prometheus queries. The Metrics tab on the [Fly.io Dashboard](https://fly.io/dashboard) provides an overview of your Fly apps using the built-in metrics stored in Prometheus. ### Managed Grafana [Grafana](https://grafana.com/) is a popular open source data visualization web application, that allows you to compose queries against data sources into dynamic, reusable dashboards. We provide a managed Grafana instance at [fly-metrics.net](https://fly-metrics.net), preconfigured with your Prometheus data source and detailed dashboards covering the full set of built-in metrics. You can also use the Explore panel to run ad-hoc queries against the preconfigured Prometheus datasource, or create/import additional dashboards for further customization or to visualize custom metrics. Switch between your Fly.io Organizations by clicking the "Switch organization" link beneath the user icon in the lower-left of the screen. ### External or self-hosted Grafana You can also configure your Prometheus endpoint with an existing Grafana installation, or [host one on Fly.io](https://github.com/fly-apps/grafana). Either way, you can set it up like this: 1. [Add](https://grafana.com/docs/grafana/latest/datasources/add-a-data-source/) a [Prometheus data source](https://grafana.com/docs/grafana/latest/datasources/prometheus/) (Settings -> Data Sources -> Add data source -> Prometheus) 2. Fill the form with the following: * HTTP -> URL: `https://api.fly.io/prometheus//` * Custom HTTP Headers -> + Add Header: * Header: `Authorization` * Value: `FlyV1 ` (for tokens created with `fly tokens create`) * Value: `Bearer ` (only for tokens from `flyctl auth token`) You're all set. We publish our [Fly.io Dashboards](https://grafana.com/grafana/dashboards/14741) to Grafana.com for use with external Grafana instances. To install, just [import the dashboard](https://grafana.com/docs/grafana/latest/dashboards/export-import/#import-dashboard) using the listed IDs. If you'd like to contribute changes to the dashboards, we have created a [repository](https://github.com/superfly/dashboards) for them. ## Alerting Fly.io doesn't include built-in alerting on metrics, so you'll need to set up alerting yourself against the Prometheus endpoint. Two common approaches: * **Grafana alerting**: connect the Fly.io Prometheus data source to a Grafana instance (see the [External or self-hosted Grafana](#external-or-self-hosted-grafana) section), then create [Grafana alert rules](https://grafana.com/docs/grafana/latest/alerting/) on queries against the data source and send the alerts to a contact point such as email, Slack, or PagerDuty. * **Prometheus and Alertmanager**: run your own [Prometheus](https://prometheus.io/) that federates from the Fly.io endpoint and use [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) to send notifications. If you don't already run Grafana or Prometheus, Fly.io's [Observability for User Apps](/blueprints/observability-for-user-apps) guide deploys a ready-made stack (VictoriaMetrics and Grafana) you can build alerts on. ## Built-in metrics Fly apps automatically publish a number of built-in metrics. [Metric types](https://prometheus.io/docs/concepts/metric_types/) are all [Gauges](https://prometheus.io/docs/concepts/metric_types/#gauge) unless otherwise marked. Metrics with names ending in `_count` are all [Counters](https://prometheus.io/docs/concepts/metric_types/#counter). [Histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) metrics with a base name of `` expose multiple series: * `_bucket{le}` * `_sum` * `_count` ### Standard Labels All published series include the following labels: * `app`: App name * `region`: [Fly.io Region](/reference/regions#fly-io-regions) * `host`: 4-character host ID (lowercase hexadecimal) * `instance`: App instance ID (for all series except `fly_edge_` and `fly_volume_`) If your app exposes custom metrics with the same labels, they will be overwritten. ### Proxy series Any app using a TCP-based handler (HTTP, TLS or straight TCP) publishes `edge` and `app` proxy metrics: Labels: * `proxy_id`: "blue" or "green" (flips when the proxy is restarted/updated) #### Edge - `fly_edge_` ``` fly_edge_http_responses_count{status} fly_edge_http_response_time_seconds{status} (Histogram) fly_edge_tcp_connects_count fly_edge_tcp_disconnects_count fly_edge_data_out (Counter, bytes) fly_edge_data_in (Counter, bytes) fly_edge_tls_handshake_errors{servername} (Counter) fly_edge_tls_handshake_time_seconds{version} (Histogram) ``` #### App - `fly_app_` ``` fly_app_concurrency fly_app_http_responses_count{status} fly_app_http_response_time_seconds{status} (Histogram) fly_app_connect_time_seconds (Histogram) fly_app_tcp_connects_count fly_app_tcp_disconnects_count ``` ### Instance series - `fly_instance_` Derived from the [`/proc` file system](https://www.kernel.org/doc/html/latest/filesystems/proc.html) of your app VMs. `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*. ``` fly_instance_memory_mem_total fly_instance_memory_mem_free fly_instance_memory_mem_available fly_instance_memory_buffers fly_instance_memory_cached fly_instance_memory_swap_cached fly_instance_memory_active fly_instance_memory_inactive fly_instance_memory_swap_total fly_instance_memory_swap_free fly_instance_memory_dirty fly_instance_memory_writeback fly_instance_memory_slab fly_instance_memory_shmem fly_instance_memory_vmalloc_total fly_instance_memory_vmalloc_used fly_instance_memory_vmalloc_chunk ``` #### Instance Load and CPU * `load_average` is derived from [`/proc/loadavg`](https://www.kernel.org/doc/html/latest/filesystems/proc.html#id11) ([`getloadavg`](https://man7.org/linux/man-pages/man3/getloadavg.3.html)). It's a ["system load average"](https://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html) measuring the number of processes in the system run queue, with samples representing averages over 1, 5, and 15 `minutes`. * `cpu` is derived from [`/proc/stat`](https://www.kernel.org/doc/html/latest/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat), and counts the amount of time each CPU (`cpu_id`) has spent performing different kinds of work (`mode`, which may be one of `user`, `nice`, `system`, `idle`, `iowait`, `irq`, `softirq`, `steal`, `guest`, `guest_nice`). The time unit is 'clock ticks' of centiseconds (0.01 seconds). The following CPU metrics are related to [CPU Performance](/machines/cpu-performance): * `cpu_baseline` is the baseline quota in number of CPUs, calculated from the CPU type and number of vCPUs. * `cpu_balance` is the accrued CPU burst balance in clock ticks (centiseconds). * `cpu_throttle` is derived from the `throttled_time` field of the cgroup [`cpu.stat`](https://docs.kernel.org/scheduler/sched-bwc.html#statistics), and counts the amount of time the CPU was throttled after exhausting its quota, in 'clock ticks' (centiseconds). ``` fly_instance_load_average{minutes} fly_instance_cpu{cpu_id, mode} (Counter, centiseconds) fly_instance_cpu_baseline (CPUs) fly_instance_cpu_balance (centiseconds) fly_instance_cpu_throttle (Counter, centiseconds) ``` #### Instance Disks - `fly_instance_disk_` Counters derived from fields 1-11 of [`/proc/diskstats`](https://www.kernel.org/doc/html/latest/admin-guide/iostats.html). The unit for `time_` series is **milliseconds**, and the unit for `sectors_` is 512-byte sectors. Labels: * `device`: Published for the ephemeral VM root disk (`vdb`) and any mounted Volume (`vdc`). ``` fly_instance_disk_reads_completed fly_instance_disk_reads_merged fly_instance_disk_sectors_read fly_instance_disk_time_reading fly_instance_disk_writes_completed fly_instance_disk_writes_merged fly_instance_disk_sectors_written fly_instance_disk_time_writing fly_instance_disk_io_in_progress fly_instance_disk_time_io fly_instance_disk_time_io_weighted ``` #### Instance Networking - `fly_instance_net_` Counters derived from [`/proc/net/dev`](https://www.kernel.org/doc/html/latest/networking/statistics.html#procfs). Labels: * `device`: interface name, either `eth0` or `dummy0` (ignore). ``` fly_instance_net_recv_bytes fly_instance_net_recv_packets fly_instance_net_recv_errs fly_instance_net_recv_drop fly_instance_net_recv_fifo fly_instance_net_recv_frame fly_instance_net_recv_compressed fly_instance_net_recv_multicast fly_instance_net_sent_bytes fly_instance_net_sent_packets fly_instance_net_sent_errs fly_instance_net_sent_drop fly_instance_net_sent_fifo fly_instance_net_sent_colls fly_instance_net_sent_carrier fly_instance_net_sent_compressed ``` #### Instance File Descriptors - `fly_instance_filefd_` Information about allocated, and maximum allowed allocated file descriptors derived from [`/proc/sys/fs/file-nr`](https://www.kernel.org/doc/html/latest/admin-guide/sysctl/fs.html#file-max-file-nr). ``` fly_instance_filefd_allocated fly_instance_filefd_maximum ``` #### Instance Filesystem - `fly_instance_filesystem_` Filesystem metrics derived from [VFS File System Information](https://man7.org/linux/man-pages/man0/sys_statvfs.h.0p.html). Labels: * `mount`: mount point name(s), `/` and if using [Volumes](/volumes), the destination name in fly.toml. ``` fly_instance_filesystem_blocks fly_instance_filesystem_block_size fly_instance_filesystem_blocks_free fly_instance_filesystem_blocks_avail ``` ### Volumes - `fly_volume_` Labels: * `id`: Volume ID If you're using [Volumes](/volumes) for any of your organization's apps, you'll be able to query these series, derived from the `LSize` and `Data%` of the volume's [thin LV](https://man7.org/linux/man-pages/man7/lvmthin.7.html). ``` fly_volume_size_bytes fly_volume_used_pct (0-100) ``` ### Postgres - `pg_` If you have a [Postgres](/unmanaged-postgres) database hosted on Fly.io, you'll automatically get the following series, published via [`postgres_exporter`](https://github.com/prometheus-community/postgres_exporter): ``` pg_stat_activity_count pg_stat_activity_max_tx_duration pg_stat_archiver_archived_count pg_stat_archiver_failed_count pg_stat_bgwriter_buffers_alloc pg_stat_bgwriter_buffers_backend_fsync pg_stat_bgwriter_buffers_backend pg_stat_bgwriter_buffers_checkpoint pg_stat_bgwriter_buffers_clean pg_stat_bgwriter_checkpoint_sync_time pg_stat_bgwriter_checkpoint_write_time pg_stat_bgwriter_checkpoints_req pg_stat_bgwriter_checkpoints_timed pg_stat_bgwriter_maxwritten_clean pg_stat_bgwriter_stats_reset pg_stat_database_blk_read_time pg_stat_database_blk_write_time pg_stat_database_blks_hit pg_stat_database_blks_read pg_stat_database_conflicts_confl_bufferpin pg_stat_database_conflicts_confl_deadlock pg_stat_database_conflicts_confl_lock pg_stat_database_conflicts_confl_snapshot pg_stat_database_conflicts_confl_tablespace pg_stat_database_conflicts pg_stat_database_deadlocks pg_stat_database_numbackends pg_stat_database_stats_reset pg_stat_database_tup_deleted pg_stat_database_tup_fetched pg_stat_database_tup_inserted pg_stat_database_tup_returned pg_stat_database_tup_updated pg_stat_database_xact_commit pg_stat_database_xact_rollback pg_stat_replication_pg_current_wal_lsn_bytes pg_stat_replication_pg_wal_lsn_diff pg_stat_replication_reply_time pg_replication_lag pg_database_size_bytes ``` ## Custom Metrics For further customization beyond built-in metrics, Fly apps can expose a metrics endpoint we'll automatically scrape every 15 seconds and send the results to Prometheus. ### Configuration Add a `[metrics]` section to your application's `fly.toml`: ```toml theme={null} [metrics] port = 9091 path = "/metrics" # default for most prometheus exporters ``` If your app uses [multiple processes](/launch/processes), you can add multiple `[[metrics]]` sections, each with its own set of `processes`: ```toml theme={null} [[metrics]] port = 9394 path = "/metrics" processes = ["web"] [[metrics]] port = 9113 path = "/metrics" processes = ["proxy"] ``` ### Instrumentation Instrument your app and expose your metrics on `0.0.0.0`. There are many supported [client libraries](https://prometheus.io/docs/instrumenting/clientlibs/) as well as off-the-shelf [exporters](https://prometheus.io/docs/instrumenting/exporters/) able to return Prometheus-formatted metrics. ## Authentication Authenticating to the Prometheus API can be achieved a few different ways, depending on the level of access you want your token to have. ### Fly Access Token As in the earlier example, a full access token can be generated with `flyctl auth token` and then passed as a bearer token in the `Authorization` header. The header looks like: ``` Authorization: Bearer THE_TOKEN ``` ### Fly org-restricted or read-only token This kind of token or "[macaroon](https://fly.io/blog/macaroons-escalated-quickly/)" can be scoped to a single organization or configured to only allow read operations, which can be safer than using a full-blown read-write token that grants access to all organizations under your account. ### Generating tokens Create an org-restricted token: ``` fly tokens create org -o THE_ORGANIZATION ``` Create a read-only org-restricted token: ``` fly tokens create readonly ``` These tokens look like this once generated: `FlyV1 fm2_lJPECAAAAAAAAC7txBAzYI6PRWhHLT...(a lot of base64-encoded text)`. #### Using tokens 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) ``` # Search logs Source: https://docs.fly.io/monitoring/search-logs Search Fly App logs in Grafana, backed by VictoriaLogs with 7-day retention and LogsQL queries. Log search is in beta and free during the beta. > [!WARNING] > > Searchable application logs are a beta feature. During the beta period, log search is free. Log search on Fly.io is backed by our own instance of [VictoriaLogs](https://docs.victoriametrics.com/victorialogs) and is exposed through a Grafana interface. We currently retain logs for 7 days. To search logs from your Fly Apps: * Click **Search logs in Grafana** in your Fly.io app dashboard, or * Open your [Grafana dashboard](https://fly-metrics.net/d/fly-logs/fly-logs) directly Learn how to build more complex queries using [LogsQL, the query language of VictoriaLogs](https://docs.victoriametrics.com/victorialogs/logsql). # Application Monitoring by Sentry Source: https://docs.fly.io/monitoring/sentry Access existing Sentry projects provisioned through Fly.io, open the dashboard with fly ext sentry, and see what happens when the one-year Team plan ends. > [!WARNING] > > **Heads up**: Provisioning new Sentry projects through Fly.io (`fly ext sentry create`) is no longer available. Existing projects keep working, and this page sticks around for the people using them. Need error tracking on a new app? [Sign up with Sentry directly](https://sentry.io/signup/) and set your `SENTRY_DSN` as an app secret. [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 former partnership with Sentry, organizations that provisioned Sentry through Fly.io received a year’s worth of [Team Plan](https://sentry.io/pricing) credits. ## Access your Sentry project Apps provisioned through Fly.io have the `SENTRY_DSN` secret set, so the environment variable is available at runtime. Most Sentry SDKs will automatically pick this up and begin sending events. You can open the Sentry dashboard for your app by running: ``` fly ext sentry dashboard ``` ## 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 Your organization received 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. > [!NOTE] > > 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 ## Updating SSO and login methods 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: 1. Go to **Organization Settings > Auth Settings** in Sentry 2. Click **Disable Fly.io Auth** 3. Sentry will email each organization member to set a password for their account This allows you to log in with email and password, and to enable other SSO providers depending on your Sentry subscription level. For details, see [https://sentry.zendesk.com/hc/en-us/articles/24206530196251](https://sentry.zendesk.com/hc/en-us/articles/24206530196251). ## Do I need to migrate anything after my plan expires? No migration steps are needed. Your existing Sentry organization, project, data, alerts, and SDK configuration remain intact. When your plan changes: * 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 For more details, see [https://sentry.zendesk.com/hc/en-us/articles/36501551064219](https://sentry.zendesk.com/hc/en-us/articles/36501551064219). # Connect to an App Service Source: https://docs.fly.io/networking/app-services Reach a process on a Fly Machine via Fly Proxy or 6PN: which address to bind, when services and IPs are needed, and curl checks for each path. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-servers.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=948492803f81a61a57aa15d769019966) There are two basic ways to talk to a process running in your Fly Machine: 1. Via Fly Proxy, the Fly.io component that handles load balancing—this is what you'll need for any public web service 2. Over the WireGuard [IPv6 private network ("6PN")](/networking/private-networking) that the app belongs to—this can be useful for, e.g., providing private supporting services to your Fly Apps ## TL;DR Here's a cheat sheet for configuring apps to be reachable by each of these means: |   | Fly Proxy | Internal (6PN) | | - | - | - | | Bind to | `0.0.0.0:` ([not UDP](#udp-is-special)) | `fly-local-6pn:` | | Needs `services` or `http_service` in config? | YES | NO | | App needs an IP? | YES ([not for Fly-Replay](#a-note-on-fly-app-ips-and-fly-replay-routing)) | NO | There's a bit more to the whole picture, which is why the rest of this document exists. ## Services Routed with Fly Proxy All services reachable from the public internet via a Fly App's global Anycast address are routed by Fly Proxy. Fly Proxy can load-balance requests for both public and private ([Flycast](/networking/flycast)) services among a Fly App's VMs. Routing to a service with Fly Proxy also enables other Fly Proxy features, such as [automatically starting and stopping Machines](/launch/autostop-autostart) in response to fluctuations in request traffic. ### Get the service listening on the right address within the VM Fly Proxy reaches services through a private IPv4 address on each VM, so the process should listen on `0.0.0.0:` (but see [A note on IPv4 and IPv6 wildcards](#a-note-on-ipv4-and-ipv6-wildcards)). #### UDP is special UDP services have to listen at a different specific address, `fly-global-services`, on their VM, due to how our UDP proxying logic works. ### Configure the service for Fly Proxy to reach it #### Public apps Define a [service](/reference/configuration#the-http_service-section) in `fly.toml` (or in the case of standalone Machines that don't belong to Fly Launch, in individual Machine configs), making sure the `internal_port` on the service definition is the same port the process is bound to inside the VM. Without this config, Fly Proxy isn't aware of the service and can't route to it. #### Private (Flycast) apps Configure a service as for a public app, but in addition: [Flycast](/networking/flycast) doesn't do HTTPS, so make sure you've defined a non-HTTPS service, and also ensure that `force_https = true` is **not** included in any HTTP service definition. ### Allocate a public or private IP to the app #### Public apps To have Fly Proxy route requests to an app from the public internet, provision at least one public Anycast IP for the app. An app with HTTP and HTTPS services defined is allocated public IPs automatically on the first run of `fly deploy`; you can confirm with `fly ips list`. #### Private (Flycast) apps To have Fly Proxy load-balance among VMs on a non-public app, [allocate a *private* (Flycast) IPv6 address](/networking/flycast) using `fly ips allocate-v6 --private`, and **remove any public IPs from the app** using `fly ips release
`. A Flycast IP can only be reached from within the private network it's allocated on, which must belong to the same Fly Organization as the Fly App it points to. > [!WARNING] > > If your configuration includes any services for Fly Proxy to route to, and the app has a public IP, that service is exposed to the whole internet. ### A note on Fly App IPs and Fly-Replay routing App-wide IP addresses (public and private) tell Fly Proxy which app to deliver a request to. If a service *only* needs to be reachable by Fly Proxy in its handling of the [Fly-Replay](/networking/dynamic-request-routing) response header, its app does not need an IP; app information is incorporated directly in the header. ## Private services on 6PN (direct WireGuard connections) If you don't need load balancing or other Fly Proxy features, you (or one of your apps) can connect directly on a VM's 6PN address (i.e., its address on its IPv6 private WireGuard network). > [!NOTE] > > By default, all of an organization's Fly Apps live on the same private network, but you can create apps on different private networks to isolate them from one another. You'd want to do this if you run separate apps for your customers, for example. ### Get the service listening on the right address within the VM Each VM has the hostname `fly-local-6pn` mapped to its own 6PN address in its `/etc/hosts` file, so to make a service reachable over the private network, bind it to `fly-local-6pn:`. Binding to a port on the IPv6 wildcard "address" (`[::]:`) also works (but see [A note on IPv4 and IPv6 wildcards](#a-note-on-ipv4-and-ipv6-wildcards)). That's it, really. There's no need to define any service in `fly.toml`, and there's no need to provision any IPs for the app. If your service works publicly but can't be reached directly over WireGuard, the problem may be that it's not listening on IPv6. ### Fly.io internal DNS and 6PN addresses Fly.io runs a specialized internal DNS service to find 6PN addresses for VMs. Queries on the various `.internal` names return either AAAA records for 6PN addresses, or a TXT record with information about your app, your network, or your Machines. Internal DNS gives us some useful presets for targeting requests. For instance, a DNS query on the address `..internal` yields the 6PN addresses of the VMs of app `` in region ``. Addressing a request to `..internal` gets it delivered to one of those VMs. Our [private networking docs](/networking/private-networking#fly-io-internal-dns) list the `.internal` names and what kind of information a DNS query returns for each. ## A note on IPv4 and IPv6 wildcards Services to be routed to using Fly Proxy need to bind to IPv4 addresses, and services to be reached over 6PN need to bind to IPv6, as described above. However, you may find that your service works even if you haven't used the addresses we specified. IPv4-mapped IPv6 addresses, if enabled on the VM, can allow IPv4 (and thus Fly Proxy) connections to a service bound on `[::]`. Further, the wildcard syntax `[::]` or `0.0.0.0` sometimes covers both IPv4 and IPv6, depending on the language, library, or application. ## Troubleshoot connections to a service ### Connect to the process from inside the VM You may want to make sure the process is doing what you think it is, inside the VM. If you should have a service running internally, you can try connecting to it with cURL from within the VM (use `fly ssh console` to pop a shell; see note \[2] below the tables). If your app's Docker image doesn't have `curl` installed, you can install it; it'll be wiped away next time the VM is restarted (e.g. on the next `fly deploy`). A HEAD request (`curl -I`) should be enough to see if you're getting a response: ```bash cmd theme={null} # curl -I http://localhost:80 ``` ```out out theme={null} HTTP/1.1 200 OK Server: nginx/1.23.4 Date: Tue, 02 May 2023 20:32:32 GMT Content-Type: text/html Content-Length: 615 Last-Modified: Tue, 28 Mar 2023 15:01:54 GMT Connection: keep-alive ETag: "64230162-267" Accept-Ranges: bytes ``` That `200 OK` means the service (nginx) is running, and listening on port 80 as anticipated. You can further check that the right HTML is being served, with `curl http://localhost:` (leaving out the `-I`). cURL is a useful tool for checking that a service is reachable where it should be. Here are some further tests that may help with understanding networking issues. We'll carry on with the example of an HTTP app. ### Connect to Public or Flycast services | Check that the service is | From | Use | | - | - | - | | Listening on the expected address and port \[1] | within the VM \[2] | `curl -I http://0.0.0.0:` \[3] | | Reachable by Anycast \[4] | anywhere | `curl -I https://.fly.dev:443` or `curl -IL http://.fly.dev:80` \[5] | | Reachable by Flycast \[6] | within the Flycast IP's 6PN \[7] | `curl -I http://.flycast:80` or `curl -I 'http://[]:80'` | ### Connect to Private 6PN services | Check that the service is | From | Use | | - | - | - | | Listening on the expected address and port \[8] | within the VM \[2] | `curl -I http://fly-local-6pn:` | | Reachable on the app via internal DNS | within the app's 6PN | `curl -I http://.internal:` | | Reachable on a particular VM | within the app's 6PN | `curl -I 'http://[]:'` | \[1] If this fails (i.e. you don't get the expected response from the service), then either (a) the service isn't functioning the way you expect (b) it's not bound to the port you pointed cURL at, or (c) it's not bound to the IPv4 address Fly Proxy uses to reach the VM, and so Fly Proxy won't be able to route to it. \[2] Pull up an interactive shell on a VM that should be running this service, with `fly ssh console`. Don't use the `fly console` command; this brings up an ephemeral VM from the app's Docker image, but doesn't start up the same process(es), so your service won't be running there. \[3] [Except UDP services.](#udp-is-special) \[4] If this fails, but the local check succeeded, the next thing to confirm is that the app or Machine config includes a properly configured `services` or `http_service` section, with `internal_port` matching `` used in this test. Also check `fly ips list`: if the app has only an IPv6 address, it won't be reachable from IPv4-only networks. You can [allocate a free shared IPv4 address](/networking/services#shared-ipv4) with `fly ips allocate-v4 --shared`. \[5] The HTTP URL always elicits a 301 redirect, because the Fly Proxy upgrades HTTP connections to HTTPS. To get cURL to follow the redirect to see if there's anything there, use the `-L` flag. This example has the services configured to handle HTTP and HTTPS requests on the conventional ports 80 and 443 respectively; if you're using different ports, substitute those in. \[6] If the check within the VM succeeded, but this step fails, check everything in note \[5], plus: Ensure that there's an HTTP service configured and `force_https` is not set to `true`; Flycast doesn't work over HTTPS. \[7] A Flycast IP [can be allocated](/networking/flycast) on a different private network from the app it points to, if both networks belong to the same org. This lets your apps reach your service in a different 6PN, if the service is configured to be available over Flycast. \[8] If this fails (i.e. you don't get the expected response from the service), then either (a) the service isn't functioning the way you expect, (b) it's not bound to the port you pointed cURL at, or (c) it's not bound to its 6PN address, and won't be reachable via the private WireGuard network (including at `.internal` addresses). # Custom domains Source: https://docs.fly.io/networking/custom-domain Point your own domain at a Fly.io app with fly certs add, set A, AAAA or CNAME records, validate the TLS certificate, and fix Cloudflare certificate issues. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-ui.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=3d33c9569cdf6a0563f123a71556507a) 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 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. ### Add your domain 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. You can add your domain with flyctl or in your app [dashboard](https://fly.io/dashboard/) under **Certificates**. For example, using the CLI: ```bash theme={null} fly certs add example.com ``` This command will show you the applicable DNS configuration options for your setup. If you are adding a wildcard domain with the CLI, put quotes around the hostname to avoid shell expansion: ```bash theme={null} fly certs add "*.example.com" ``` Use `fly certs check ` to check the certificate status and validation progress, or `fly certs setup ` to view all setup options. ### Configure DNS records 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. Choose the DNS setup that matches your needs: > [!NOTE] > > **Important:** To issue or renew a certificate, Fly.io needs to verify your domain through at least one of: an IPv6 address (AAAA record) pointing to your app, a [CNAME `_acme-challenge`](#dns-challenge), or a [`_fly-ownership` TXT record](#domain-ownership-verification). #### A and AAAA records (recommended) Use A and AAAA records for most direct connections to your app. These records point your domain directly to your app's IP addresses. 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 IPv4 and IPv6 addresses, allocate them with `fly ips allocate`. #### CNAME records 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. 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 setup`. 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. #### DNS Challenge Use the DNS-01 challenge when you need a wildcard certificate, or when you want to generate the certificate before directing traffic to your app. 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 setup`. #### Domain ownership verification Use a `_fly-ownership` TXT record to verify domain ownership when other validation methods aren't available for your setup. This is useful when: * Your domain is behind a CDN or proxy that prevents direct validation * You're importing a [custom certificate](#use-your-own-certificate) * You don't want to allocate or use IPv6 addresses The required record will be shown in your dashboard, or in the output from `fly certs setup`. You can include multiple values in a single TXT record by separating them with semicolons. ### Certificate validation After you have configured your DNS, Fly.io automatically validates your domain ownership and issues certificates. This happens through one of these methods: * **TLS-ALPN challenge**: Validates through a TLS handshake with Fly Proxy. This is the preferred method and works automatically for direct connections. * **HTTP-01 challenge**: Validates by requesting a specific file from your domain. Used automatically when TLS-ALPN isn't available. * **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. ## Use your own certificate In addition to the automatic Let's Encrypt certificates that Fly.io manages for you, you can upload your own TLS certificate and private key. Upload your certificate and private key in PEM format using the CLI: ```bash theme={null} fly certs import example.com --fullchain fullchain.pem --private-key private-key.pem ``` You can also import certificates from your app dashboard under **Certificates**. After uploading a custom certificate, it may show a status of `pending_ownership` until domain ownership is verified. Ownership is verified by an IPv6 DNS record pointing at your app, or by the `_fly-ownership` TXT record. Custom and Fly-managed (Let's Encrypt) certificates can coexist for the same hostname. When both are present, your custom certificate is served as the primary certificate, and the Fly-managed certificate acts as an automatic fallback. ## 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. Use `--custom` to remove only a custom certificate, or `--acme` to stop ACME issuance only. ## Teaching your app about custom domains Your application code needs to know how to accept custom domains and adjust the responses accordingly. If you're hosting content on behalf of your users, this typically means mapping the incoming hostname to a particular ID in your application database. 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. ## Supported top-level domains We support the top-level domains on the [IANA list](https://data.iana.org/TLD/tlds-alpha-by-domain.txt). Note that we only periodically update top-level domain support and there might be a delay before we add support for new top-level domains. ## Troubleshoot certificate creation ### Certificate creation or validation seems to hang, stall, or fail *Let's Encrypt™* is a free, automated, and open certificate authority that Fly.io uses to issue TLS certificates for custom domains. However, Let's Encrypt imposes certain rate limits to ensure fair usage. If you encounter issues when creating or validating a certificate for a custom domain on Fly.io, it's possible that you've hit these rate limits. The following [rate limits](https://letsencrypt.org/docs/rate-limits/) from Let's Encrypt apply: * Certificates per Registered Domain: 50 per week * Duplicate Certificate limit: 5 per week * Failed Validation limit: 5 failures per hostname, per hour Note that certificate renewals don’t count against your Certificates per Registered Domain limit. If you encounter issues when adding or validating a certificate for a custom domain on Fly.io, you can use the following methods to troubleshoot: * Use [Let's Debug](https://letsdebug.net/): A diagnostic tool/website to help figure out why you might not be able to issue a certificate for Let's Encrypt. Using a set of tests, it can identify a variety of issues, including: problems with basic DNS setup, problems with nameservers, rate limiting, networking issues, CA policy issues, and common website misconfigurations. * Wait and retry: If you've hit a rate limit, you'll need to wait until the rate limit window passes before you can successfully create or validate a certificate again. We don’t have a way to reset it. The best way to avoid hitting rate limits is to use staging environments and domains for testing and development, and to carefully plan your certificate issuance to stay within the limits. Avoid failed validation by ensuring that your DNS records are correctly configured, with no conflicting records. If you're building a platform on top of Fly.io, and you expect that your users will frequently delete and then recreate the same resources within a short window, consider implementing "soft delete" logic into your platform that retains the Fly.io resources for a period of time, negating the need to recreate certs frequently. ### I use Cloudflare, and there seems to be a problem issuing or validating my Fly.io TLS certificate If you're using Cloudflare's proxying feature (orange cloud), verify domain ownership by adding a [`_fly-ownership` TXT record](#domain-ownership-verification). Once ownership is verified, Fly.io can issue certificates using the HTTP-01 challenge through Cloudflare's proxy. For this to work: 1. **Verify domain ownership**: Add a `_fly-ownership` TXT record with the values shown in your certificate details. See [Domain ownership verification](#domain-ownership-verification). 2. **Set SSL mode**: Use "Full" or "Full (Strict)" SSL mode in Cloudflare. "Flexible" mode can cause redirect loops. Depending on your Cloudflare configuration, HTTP-01 challenges may be blocked and generation may still fail. In these cases, you can bypass ACME certificate generation entirely by importing a [Cloudflare Origin Certificate](/networking/understanding-cloudflare#using-a-cloudflare-origin-certificate) and verifying ownership with the same `_fly-ownership` TXT record. ## Related reading * [Networking overview](/networking) Broader look at how Fly.io handles routing, DNS, IPs, and certificates. * [Certificates API reference](/machines/api/certificates-resource) Use the Machines API to automate cert provisioning and domain verification. * [TLS termination by Fly Proxy](/security/tls-termination) — How Fly.io handles HTTPS for your apps and manages TLS certificates automatically behind the scenes. * [Understanding Cloudflare](/networking/understanding-cloudflare) What happens when you put Fly.io behind Cloudflare, and how to avoid common pitfalls. # Custom private networks Source: https://docs.fly.io/networking/custom-private-networks Isolate tenants on custom 6PNs with fly apps create --network, connect them via Flycast or fly-replay, and read network IDs from IPv6 addresses. ![Illustration by Annie Ruygt of two apps sitting on clouds and talking to each other on the telephone](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/custom-private-networks.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=3b6f8c0a8addfecb0634beaf9e281dae) You can isolate users, data, or code using custom private networks. Every organization gets a default [private network](/networking/private-networking) (6PN) and all the apps in that network can communicate privately using `.internal` domains. When you create a new app, you can choose to place it on a distinct custom 6PN within your organization. Apps on separate 6PNs can never communicate unless explicitly configured to do so. ## When to use custom private networks Custom private networks (6PNs) are a good solution for tenant isolation within a single organization. If you run a *Something*-as-a-Service platform on top of Fly.io, then you can create an app on a custom 6PN per customer for isolation. For example, you could have an app on a custom 6PN for each customer instance of your service, or for each customer running untrusted code on your platform. Customer Machines can communicate freely within their app (or between apps in their network), but won't be able to reach Machines or apps on other 6PNs. Depending on your requirements for billing and app management, separate organizations per customer might be a better option for tenant or client isolation. You can use [unified billing](/about/billing#unified-billing) to manage billing for multiple organizations, or have each organization billed separately. ## Network names and IDs Network names are unique within your organization. We permanently associate a network ID with the network name the first time you specify the name and we never re-use that network ID within your organization. If you specify an existing network name when you create an app, we place the app on the associated network. If you destroy all the apps on a custom 6PN, the network technically still exists. It's a best practice to use a formula for naming custom 6PNs, especially if you're going to have a lot of them or if you need to add more than one app to each custom 6PN. For example, `customerID-network` or `appName-someID-network`. Network names can have letters, numbers, and dashes, but must start with a letter. ## Create a custom private network > [!NOTE] > > You can't add an existing app to a custom 6PN or change an app's 6PN. An app can only exist in one 6PN. Create a custom 6PN with the Machines API when you create an app with a `POST /v1/apps` request. For example: ```json theme={null} curl -i -X POST \\ -H "Authorization: Bearer ${FLY_API_TOKEN}" -H "Content-Type: application/json" \\ "${FLY_API_HOSTNAME}/v1/apps" \\ -d '{ "app_name": "my-app-name", "org_slug": "personal", "network": "my-custom-network-name" }' ``` See the [Machines API docs](/machines/api) for more information about using the API. Create a custom 6PN with flyctl when you create an app: ``` fly apps create --network ``` The `fly apps create` command creates an "empty" app with no Machines. You'll need to manually create Machines for the app using flyctl or the Machines API. ## Connect apps in different 6PNs Private apps on different 6PNs can't communicate without being explicitly configured to do so. ### Public apps If an app has a publicly reachable service, then apps on different 6PNs can reach that service using the public IP addresses. ### Private apps with Flycast You can use a Flycast private IPv6 address to expose an app on one 6PN to another 6PN; the app won't be accessible via Flycast from its own network. When you assign the Flycast address, use the `--network` option to specify the name of the network from which requests will originate. For example: ```bash theme={null} fly ips allocate-v6 --private --network custom-network-name ``` Learn more about using [Flycast](/networking/flycast). ### Forward requests with the `fly-replay` response header The `fly-replay` response header tells Fly Proxy to forward HTTP requests to another app or Machine in your organization. You can use `fly-replay` to deliver requests to customer apps or Machines on custom 6PNs without having to expose them publicly. For example, you could receive HTTP requests on a public "router" app and then forward the requests to specific apps or Machines on different customer 6PNs. The customer apps need to have services configured to expose a port on which to receive the requests. Make sure the customer apps don't have public IP addresses: apps created with the Machines API or with `fly apps create` don't get public IPs by default, but most apps created with `fly launch` do. Learn more about using the [`fly-replay` response header](/networking/dynamic-request-routing#how-fly-replay-works). ## Connect directly to Machines on custom 6PNs As an administrator with a multi-tenant setup, you might need to troubleshoot customer issues. You can use flyctl's networking commands to reach Machines on a customer's 6PN within your organization, including: * `fly ssh console --app ` or `fly ssh console --machine ` to SSH into an app Machine. See the [`fly ssh console` docs](/flyctl/cmd/fly_ssh_console). * `fly ssh sftp` commands to work with files on an app Machine. See the [`fly ssh sftp` docs](/flyctl/cmd/fly_ssh_sftp). * `fly proxy [remote_host] --app ` to create a WireGuard tunnel to an app Machine. See the [`fly proxy` docs](/flyctl/cmd/fly_proxy). ## Check which network an app or Machine is on You can retrieve the network name for all apps in your organization and you can also verify that apps or Machines are on different networks by examining their private IPv6 addresses. ### Get network names Right now the only way to get an app's network name is by listing all the apps in an organization with the Machines API. `GET /v1/apps?org_slug=` **Example response:** ```json theme={null} { "total_apps": 22, "apps": [ { "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" }, ... ``` See the [Machines API docs](/machines/api) for more information about using the API. ### Network ID embedded in private IPv6 addresses Each 6PN is identified by a network ID, which is encoded into 32-bits of each private IPv6 address. You can confirm whether two Machines are on different networks by comparing their private IPv6 addresses. You can get a Machine's 6PN address in any of the following ways: * run `fly machines list -a my-app-name` * run `fly machines status ` * from the Machine's `FLY_PRIVATE_IP` environment variable * [list the Machines in an app](/machines/api/machines-resource#list-machines) or [get a specific Machine config](/machines/api/machines-resource#get-a-machine) using the Machines API All Fly.io private IPv6 addresses start with `fdaa`. The 32-bits following `fdaa` identify the 6PN. For example, the following addresses belong to Machines on different 6PNs: ``` Machine 1: fdaa:2:45b:a7b:174:db43:d3c6:2 Machine 2: fdaa:9:d844:a7b:e:33bd:e8b1:2 ``` Machine 1's network ID is embedded in `:2:45b:` and Machine 2's network ID is embedded in `:9:d844:`. [Flycast](/networking/flycast) addresses also include the network ID in the same 32-bit grouping. Run `fly ips list` to get an app's private IPv6 address. Learn about [6PN addresses in detail](/networking/private-networking#6pn-addresses-in-detail). # Dynamic Request Routing with fly-replay Source: https://docs.fly.io/networking/dynamic-request-routing Route requests on Fly.io between regions, Machines, or apps with the fly-replay response header, including JSON replays, replay caching, and fly-force-region. 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 responds to a request with a `fly-replay` response, [Fly Proxy](/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 * Cross-app request routing within your organization * Implementing sticky sessions [YouTube video](https://www.youtube.com/embed/riCh9Xeuf0s) ## Replay Header Format Your app can add a `fly-replay` header to its response. The `fly-replay` header accepts the following fields: | Field | Description | | - | - | | `region` | The region(s) to route the request to. Accepts comma-separated list of [region codes](/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 to route to | | `state` | Optional string included in `fly-replay-src` header on replay | | `elsewhere` | If `true`, excludes responding Machine from next load-balance | | `timeout` | Duration to attempt the replay before giving up (e.g. `10s`, `800ms`) | | `fallback` | If the replay fails, route back to the original Machine. `force_self` or `prefer_self` (see [Replay Timeout and Fallback](#replay-timeout-and-fallback)) | ### Example Usage 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 ``` Route to another app: ``` fly-replay: app=target-app ``` > [!NOTE] > > By default, cross-app routing works within the same organization and network. To route across networks, see [Cross-Network Routing](#cross-network-routing). To route to apps in other organizations, see [Cross-Organization Routing](#cross-organization-routing). You can combine multiple fields: ``` fly-replay: region="sjc,any";app=target-app ``` Route to another app with a timeout and fallback to the original Machine: ``` fly-replay: app=my-worker;timeout=10s;fallback=force_self ``` > [!NOTE] > > **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 Timeout and Fallback You can set a `timeout` and `fallback` on a replay to handle cases where the replay target is unreachable. **`timeout`** sets how long the proxy tries to reach the replay target. The actual duration may slightly exceed this value. Accepts duration strings like `10s`, `500ms`. Without `fallback`, a timeout makes the replay error faster instead of waiting for the default error timeout. **`fallback`** tells the proxy to route the request back to the Machine that issued the replay if the replay fails due to timeout, exhausted retries, or no available candidate: * `force_self`: Route back to the exact Machine that issued the replay. Returns a proxy error if that Machine is no longer available. * `prefer_self`: Try the original Machine first, but fall back to any Machine in the original app if it is unavailable. When a fallback triggers, the original Machine receives the request again with a [`fly-replay-failed`](#the-fly-replay-failed-header) request header containing details about the failed replay attempt. Since this is still the original request, your app can respond with a useful error instead of the client receiving a generic proxy error. > [!NOTE] > > **Note**: Fallback requests cannot themselves issue `fly-replay` responses. ## 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](/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 to route to | | `state` | Optional string included in `fly-replay-src` header on replay | | `elsewhere` | If `true`, excludes responding Machine from next load-balance | | `timeout` | Duration to attempt the replay before giving up (e.g. `"10s"`, `"800ms"`) | | `fallback` | If the replay fails, route back to the original Machine. `"force_self"` or `"prefer_self"` (see [Replay Timeout and Fallback](#replay-timeout-and-fallback)) | | `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 theme={null} { "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" } ] } } ``` Route to another app with a timeout and fallback: ```json theme={null} { "app": "my-worker", "timeout": "10s", "fallback": "force_self" } ``` ## 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] > > **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 theme={null} { "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 theme={null} [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 theme={null} [[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](/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 theme={null} { "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](/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] > > **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 * Your app must use the [http handler](/networking/services#http-handler) * Requests larger than 1MB cannot be replayed * Field combinations must be logically valid (e.g., don't specify both app and instance if instance isn't in that app) 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-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 `timeout` and `fallback` fields cannot be set in the `fly-replay` intended to be cached * 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: | Field | Description | | - | - | | `instance` | ID of Machine that sent fly-replay | | `region` | Region request was replayed from | | `t` | Timestamp (microseconds since Unix epoch) | | `state` | Contents of original state field, if any | 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) 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](/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. ### The fly-replay-failed Header When a replay [fallback](#replay-timeout-and-fallback) triggers, Fly Proxy delivers the request back to the original Machine with a `fly-replay-failed` request header. This header contains semicolon-separated metadata about the failed replay attempt: | Field | Description | | - | - | | `instance` | ID of Machine the replay was targeting | | `app` | App the replay was targeting | | `region` | Region the replay was targeting | | `replay_source` | ID of the Machine that originally issued the replay | | `reason` | Why the replay failed: `timeout`, `retries_exhausted`, or `no_candidate` | | `elapsed_ms` | Time in milliseconds spent attempting the replay | Example header value: ``` fly-replay-failed: instance=00bb33ff;app=target-app;region=iad;replay_source=11aa44ee;reason=timeout;elapsed_ms=10000 ``` Your app can use this header to detect that a fallback occurred and respond accordingly, for example by serving a helpful error to the client. ### 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: ### The fly-prefer-region Header ``` fly-prefer-region: ams ``` 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-force-region Header ``` fly-force-region: ams ``` Attempts to route directly to specific region(s). Unlike `fly-prefer-region`, the Fly Proxy will not fall back to requesting the nearest region if it cannot reach any machine in the listed region(s). Like `fly-prefer-region`, you may also specify multiple regions in order of preference: ``` fly-force-region: iad,ord,us ``` ### 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 ``` fly-force-instance-id: 90801679a10038 ``` 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] > > **Note**: Get Machine IDs using `fly status` or `fly Machines list`. ## Common Use Cases ### Multi-Region Databases When using [global read replicas](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication), use `fly-replay` to ensure write operations go to the primary region: ``` fly-replay: region=sjc ``` See our blueprint for [Multi-region databases and fly-replay](/blueprints/multi-region-fly-replay) for a complete implementation guide. ### Cross-App Routing Use `fly-replay` to implement routing layers or FaaS-style architectures: ``` fly-replay: app=customer-function-app ``` This allows you to build router apps that can dynamically route requests to other apps. By default, cross-app routing works within the same organization and the same [private network](/networking/private-networking). ### Cross-Network Routing By default, `fly-replay` with `app=` only routes to apps within the same private network. If you have multiple networks within your organization and need to replay requests across them, you can enable cross-network replays for your organization. > [!NOTE] > > **Note**: Cross-network replay restrictions only apply to same-org replays. Cross-organization replays (via the [allowlist](#cross-organization-routing)) are not affected by this setting. #### Enabling Cross-Network Replays Only organization admins can manage this setting. You can enable cross-network replays via the dashboard or CLI. **Dashboard:** Go to your organization's settings page and find the **Routing** section. Use the **Cross-Network Replay** toggle to enable or disable cross-network replays. **CLI:** Check the current status: ```bash theme={null} fly orgs cross-network-replays status --org ``` Enable cross-network replays: ```bash theme={null} fly orgs cross-network-replays enable --org ``` Disable cross-network replays: ```bash theme={null} fly orgs cross-network-replays disable --org ``` ### Cross-Organization Routing By default, `fly-replay` with `app=` only works for apps within the same organization. To allow replay requests from apps in other organizations, you must configure an allowlist on the target organization. #### Configuring Allowed Replay Sources Only organization admins can manage the replay sources allowlist, and must also have read permissions or higher on the source organization being added. You can configure replay sources via the dashboard or CLI. **Dashboard:** Go to your organization's settings page and find the **Routing** section. Under **Cross-Organization Routing**, you can add or remove organizations that are allowed to send replay requests to apps in your organization. **CLI:** List organizations currently allowed to send replays: ```bash theme={null} fly orgs replay-sources list --org ``` Add organizations to the allowlist interactively: ```bash theme={null} fly orgs replay-sources add --org ``` Remove organizations from the allowlist interactively: ```bash theme={null} fly orgs replay-sources remove --org ``` # Egress IP addresses Source: https://docs.fly.io/networking/egress-ips Allocate static egress IPs on Fly.io for third-party allowlists, with per-region app-scoped IPv4 and IPv6 pairs, caveats, and migration from machine-scoped IPs. ![Illustration by Annie Ruygt of two happy Machines sitting on clouds](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Egress-IP.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=f18ccc9f495a7e8b884ea6a6e1f16d3b) ## Overview * By default, outbound (egress) IPs from Fly Machines are **unstable** and may change. * You can allocate **static egress IPs** for an app (both IPv4 and IPv6) via `fly ips allocate-egress`. * App-scoped static egress IPs are per-region: you need one for each region where you have machines. * Static egress IPs come with trade-offs: they cost more, and limit how many machines you can run at once. * Legacy machine-scoped static egress IPs are still available, but are no longer recommended due to their limitations and quirks. *** ## 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 (App-Scoped) App-scoped static egress IPs can be shared between multiple machines in a region belonging to the same app, and will not be deleted when machines are recreated. They are recommended over our legacy machine-scoped static egress IPs. ### Allocate an App-scoped Static Egress IP ```bash theme={null} fly ips allocate-egress --app -r ``` This allocates a pair of static egress addresses, IPv4 and IPv6, for your app in a region. If your app has Machines in multiple regions, you must allocate at least 1 app-scoped static egress IP address **per region**. Machines can only use static egress IPs that were allocated in their own region. > [!NOTE] > > You can allocate multiple pairs of IPv4 and IPv6 static egress addresses in the same region. Machines will randomly choose a pair from all static egress IPs available in the region. ### View and Manage ```bash theme={null} fly ips list fly ips release-egress ``` App-scoped egress IPs are only released when you explicitly run `fly ips release-egress`. They persist across Machine destruction and deployments. ### Billing Each app-scoped IPv4 static egress address costs \$3.60/mo, billed hourly. IPv6 addresses are allocated alongside IPv4 and are not billed separately. ### Caveats * Each static egress IP can support up to 64 Machines. If you need more than 64 Machines in one region, you will need to allocate multiple static egress IPs. * When using app-scoped static egress IPs, a Machine can make up to 1024 concurrent connections to *each* destination IP address. There is no limit on the *total* number of concurrent connections. > [!NOTE] > > We do not expect this to be a concern for most apps. However, feel free to talk to us if this limits your use case! * When you have multiple static egress IPs assigned in one region, there is currently no way to specify exactly which IP each machine will use. * There may be delays when egress IPs are applied to Machines: * Right after allocating a new egress IP, it will be applied to all existing Machines in the region after a short delay. Allocating multiple pairs of static egress IPs will not help in this case. * When creating a new Machine in an app that already has an egress IP assigned, there may be a delay before the Machine can use the egress IP. This delay may be more noticeable with more Machines or during bluegreen deployments. Allocating multiple pairs of static egress IPs can help alleviate this issue. * `flyctl` surfaces warnings when these limits are approached during Machine creation, deployments, and IP management. ### Interaction with Machine-Scoped Egress IPs App-scoped and machine-scoped egress IPs are not intended to be used together. If a Machine has a machine-scoped egress IP, it takes precedence over any app-scoped egress IP in the same region. This behavior may change in the future. *** ## Static Egress IPs (Machine-Scoped) In the past, we supported machine-scoped egress IPs that are statically bound to individual machines rather than apps. This feature is deprecated, and all new apps should use [app-scoped static egress IPs](#static-egress-ips-app-scoped) instead. However, if you already have machine-scoped static egress IPs assigned, they continue to work and you may still view and manage them. You can also "promote" them into app-scoped egress IPs. ### View and Manage ```bash theme={null} fly machine egress-ip list --app fly machine egress-ip release --app ``` ### Migrate to App-scoped Egress IPs You can migrate from machine-scoped egress IPs to app-scoped egress IPs by "promoting" a subset of existing machine-scoped egress IPs to be app-scoped. This allows you to keep external allowlist configuration unchanged while switching to app-scoped egress IPs. The promotion process is mostly seamless, but a short window of downtime is possible while the egress IP is removed from the original machine and reassigned to the app. ```bash theme={null} fly machine egress-ip promote --app ``` > [!NOTE] > > Promoted egress IPs retain the original machine's region. > If your app has machines in multiple regions, promote at least one for each region. > > You should also remove any remaining machine-scoped egress IPs that are no longer needed. ### Caveats Because legacy static egress IPs are **per-machine**, not per-app: * IPs are released when a machine is destroyed. * IPs don’t automatically transfer across deploys. * Bluegreen deployments will replace machines—and their IPs. * Deployment-time jobs may bypass egress routing. * Extra latency and connectivity issues are possible in some regions. > [!NOTE] > > Machine-scoped static egress IPs are billed per hour per machine. *** ## Best Practices * Use static egress only when required. * Test connectivity after assigning egress IPs. * Monitor for failures during deploy-time migrations. # Flycast - Private Fly Proxy services Source: https://docs.fly.io/networking/flycast Route private app traffic through Fly Proxy with Flycast on Fly.io: launch with fly launch --flycast, allocate a private IPv6, and reach apps from other orgs. ![Illustration by Annie Ruygt of apps resting on clouds, all connected by pathways](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/flycast-private-proxy.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=efdd45886f211fe118dfe1adc6b37abe) Flycast provides network addressing and routing for private apps on Fly.io private networks. With Flycast, requests to your private apps get routed through the Fly Proxy, rather than Machine-to-Machine via default [private networking](/networking/private-networking). And unlike private networking using `.internal` addresses you don't need to keep Machines running for the app to be reachable. Use Flycast within your organization's private network to: * Have [Fly Proxy autostop/autostart](/launch/autostop-autostart) Machines based on network requests. * Get Fly Proxy's [geographically aware load balancing](/reference/load-balancing) for private services. * Connect to a service from another app that can't use DNS. * Connect from third-party software, like a database, that doesn't support round-robin DNS entries. * Access specific ports or services in your app from other Fly.io organizations. * Use advanced Fly Proxy features like TLS termination or PROXY protocol support. ## Flycast quickstart The fastest way to create a new private app with a Flycast address: ``` fly launch --flycast ``` Access the services on the Flycast private IPv6 address, or with `my-app-name.flycast`, from other apps in the organization's default private network. ## Flycast requirements To use Flycast for your app, you need: * A Flycast private IPv6 address for your app on one of your Fly.io organization networks. * Your app to bind to `0.0.0.0:port`. (Binding to `fly-local-6pn:` won't work for Flycast.) * Services configured in your app's `fly.toml` with an [`[http_service]`](/reference/configuration#the-http_service-section) or [`[services]`](/reference/configuration#the-services-sections) section. Don't use `force_https`; Flycast is HTTP-only. > [!WARNING] > > **Warning:** If you have public IP addresses assigned to your app, then services in `fly.toml` are exposed to the public internet. Verify your app's IP addresses with `fly ips list`, and remove public IP addresses as needed with: `fly ips release [ip address] [ip address] ...`. ## Allocate a Flycast address A Flycast address is an app-wide private IPv6 address that Fly Proxy can route to over the private network. By default, the Flycast IP address is allocated on an app's default organization network. There are a few ways to add a Flycast address to an existing app. Allocate a Flycast address on deploy: ``` fly deploy --flycast ``` Allocate a Flycast address without deploying: ```bash theme={null} fly ips allocate-v6 --private ``` ### Access an app from another organization You can use Flycast to expose an app in one Fly.io organization to another Fly.io organization; the app won't be accessible via Flycast from its own organization. Use the `--org` option when you allocate the Flycast address: ```bash theme={null} fly ips allocate-v6 --private --org my-other-org ``` ### Access an app from another private network You can also use Flycast to expose an app on one private network to another private network in your organization; the app won't be accessible via Flycast from its own network. Use the `--network` option to specify the network from which requests will originate: ```bash theme={null} fly ips allocate-v6 --private --network custom-network-name ``` Learn more about [custom private networks](/networking/custom-private-networks). ## Flycast and Fly.io DNS Flycast addresses can also be found by using the Fly.io DNS. If an app has a Flycast address allocated to it, there will be an AAAA record at `my-app-name.flycast`. ## More Flycast * [Autostop/autostart for private apps](/blueprints/autostart-internal-apps) * [Run private apps with Flycast](/blueprints/private-applications-flycast) # Networking Source: https://docs.fly.io/networking/index Fly.io networking docs: Anycast public services, 6PN private networking, Flycast, egress IPs, fly-replay routing, custom domains, UDP, and TLS. Networking on Fly.io. [YouTube video player](https://www.youtube-nocookie.com/embed/vU9xcRCX7-U?si=pH-Cj3S5IBvr-hhr) * **[Connect to an App Service](/networking/app-services):** An overview of how to connect to your app over the private WireGuard network (6PN), and how to make your app reachable from the internet. * **[Public networking](/networking/services):** Details about public network services on Fly.io, including allocating IP addresses, finding a Machine's outbound IP, connection handlers, and redirects. * **[Private networking](/networking/private-networking):** Learn about Fly.io's IPv6 private network (6PN) and DNS on Fly Machines. * **[Custom Private Networks](/networking/custom-private-networks):** Isolate users, data, and code on custom private networks. * **[Flycast - Private Fly Proxy Services](/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](/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](/networking/dynamic-request-routing):** Use Fly.io request and response headers to customize request routing to regions, apps, and even specific Machines. * **[Custom domains](/networking/custom-domain):** Add a custom domain for your app and troubleshoot certificate creation. * **[Understanding Cloudflare](/networking/understanding-cloudflare):** How to use Cloudflare on Fly.io. * **[HTTP request headers](/networking/request-headers):** Fly.io-specific and standard HTTP headers added by the HTTP connection handler. * **[Run UDP services](/networking/udp-and-tcp):** How to set up apps that use UDP. * **[TLS support](/networking/tls):** Supported TLS cipher suites. ## Related topics * [Fly Proxy](/reference/fly-proxy) * [How Fly Proxy does load balancing](/reference/load-balancing) * [Fly Regions](/reference/regions) # Private Networking Source: https://docs.fly.io/networking/private-networking Connect apps over the private 6PN network on Fly.io using .internal DNS, and link your laptop or outside services to it with a WireGuard VPN from flyctl. ![Illustration by Annie Ruygt of computers that look like buildings connected through underground tunnels](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/private-networking.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=9dcfa16ddb0d4b0a13aa6a61c7aeddec) Fly Apps in an organization are connected by a mesh of WireGuard tunnels using IPv6 called a 6PN. Private networking over your 6PN is always available to apps by default; you don't have to do anything special to get it. Apps within the same organization are assigned special addresses (6PN addresses) tied to 6PN. Those applications can talk to each other because of their 6PN addresses, but applications from other organizations can't. The Fly.io platform won't forward packets between different 6PNs, unless you explicitly allow it, for example when you [allocate a Flycast address](/networking/flycast#allocate-a-flycast-address). You can connect apps running outside of Fly.io to your 6PN using WireGuard. You can even connect your dev laptop to your 6PN. To do that, you'll use flyctl, the Fly.io CLI, to generate a [WireGuard configuration that has a 6PN address](#private-network-vpn). ## Fly.io `.internal` DNS 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](/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`. 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. 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. > [!NOTE] > > **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) repo for examples that use the `.internal` domains. ## Listening for connections on 6PN addresses When deploying a Fly Machine, we alias the 6PN address of the Machine to `fly-local-6pn` in the Machine's `/etc/hosts` file. Your app's service needs to bind to/listen on `fly-local-6pn` to be accessible via its 6PN address. For example, if you have a service running on port 8080, then you need to bind it to `fly-local-6pn:8080` for it to be accessible to other Machines over the 6PN. > [!NOTE] > > `fly-local-6pn` is to 6PN addresses as `localhost` is to 127.0.0.1, so you can also bind directly to the 6PN address itself. Learn more about [connecting to app services](/networking/app-services). ## Connect a Fly Machine to the Fly.io DNS The custom Fly.io DNS server is always available at the IPv6 address `fdaa::3`. If you want to get fancy, you can install `dig` on the Machine and query the DNS directly. For example: ```bash cmd theme={null} root@f066b83b:/# dig +short aaaa my-app-name.internal @fdaa::3 ``` ```output output theme={null} fdaa:0:18:a7b:7d:f066:b83b:2 ``` When deploying a Fly Machine, we overwrite `/etc/resolv.conf` to point the DNS server to `fdaa::3`. Since this is the default configuration we set up for Machines on Fly.io, you probably don't need to do anything special to make this work. In rare cases, such as having an unusual file system layout, or using a networking stack that needs the nameserver explicitly indicated, you might need to point your app to the Fly.io DNS yourself. From any Fly Machine, the nameserver is always at `fdaa::3`. ## 6PN addresses in detail > [!NOTE] > > **Note:** 6PN addresses directly connect one Fly Machine with another, bypassing the Fly Proxy. To use Fly Proxy features like autostop/autostart on your private network, you can use [Flycast](/networking/flycast). Most of the time, the `.internal` DNS is all you'll need for routing. If you need more complicated routing, you might be able to take advantage of the structure of 6PN addresses in your app's design. Rather than a single address, each Fly Machine is assigned a `/112` 6PN subnet, which is structured as follows: |   |   |   | | - | - | - | | `fdaa` | 16 bits | ULA prefix | | network | 32 bits | network identifier | | host | 32 bits | host server identifier | | machine | 32 bits | fly machine identifier | | — | 16 bits | free space | > [!WARNING] > > **Caution:** 6PN addresses are **not** static and will change over time, for various reasons. If you need an unchanging method to address an individual Fly Machine, you can use the domain `.vm..internal`. The machine identifier portion of the 6PN address is not related to the 14 character Machine ID; the two are independent. A Fly Machine's current 6PN address can be found in the environment variable `FLY_PRIVATE_IP`. A Machine's 6PN address is not static, so do not assume that a Fly Machine's Machine ID can be permanently mapped to a particular 6PN address. 6PN addresses will change when an app is moved into a new organization, or when a Fly Machine is migrated onto a new host server. However, a 6PN address change can only happen on a reboot, so supplying a procedure to check for a change in 6PN address on Machine startup is sufficient to handle this event. ## Custom private networks You can create additional private networks within your organization. Custom private networks are useful when you need to isolate tenants or users for security purposes. For example, if you run a software-as-service platform on top of Fly.io, and your customers are running untrusted code on Machines or you want every customer to have their own secure app. Learn more about [custom private networks](/networking/custom-private-networks) and how to create them. ## Private Network VPN You can use the [WireGuard](https://wireguard.com/) VPN to connect to the 6PN private network. WireGuard is a flexible and secure way to plug into each one of your Fly.io organizations and connect to any app within that organization. ### Set up a private network VPN To set up your VPN, you'll use flyctl to generate a tunnel configuration file with private keys already embedded. Then you can load that file into your local WireGuard application to create a tunnel. Activate the tunnel and you'll be using the internal Fly.io DNS service which resolves `.internal` addresses - and passes on other requests to Google's DNS for resolution. #### 1. Install your WireGuard App Visit the [WireGuard](https://www.wireguard.com/install/) site for installation options. Install the software that is appropriate for your system. Windows and macOS have apps available to install. Linux systems have packages, typically named wireguard and wireguard-tools, you should install both. #### 2. Create your tunnel configuration To create your tunnel, run: ```bash theme={null} fly wireguard create ``` Select the organization you want the WireGuard tunnel to work with: ```output theme={null} ? Select organization: [Use arrows to move, type to filter] > My Org (personal) Test Org (test-org) ``` The `fly wireguard create` command configures the WireGuard service and generates a tunnel configuration file, complete with private keys which cannot be recovered. This configuration file will be used in the next step. First, save the configuration file: ```output theme={null} !!!! WARNING: Output includes private key. Private keys cannot be recovered !!!! !!!! after creating the peer; if you lose the key, you’ll need to remove !!!! !!!! and re-add the peering connection. !!!! ? Filename to store WireGuard configuration in, or 'stdout': mypeer.conf Wrote WireGuard configuration to 'mypeer.conf'; load in your WireGuard client ``` We suggest you name your saved configuration with the same name as the peer you have created. Add the extension `.conf` to ensure it will be recognized by the various WireGuard apps as a configuration file for a tunnel. Note that the name (excluding the `.conf` extension) shouldn't exceed 15 characters since this is the maximum length for an interface name on Linux. > [!NOTE] > > **Important:** If you want to interact with your peer using its name, such as queries to `_peer.internal` or `._peer.internal`, then you need to specify a name when you create the tunnel. > > Defaults are used when you don't specify a `region` and `name` in the `fly wireguard create` command. Default generated names start with `interactive-*` and we filter `interactive-*` out of DNS (because of the sheer volume of them). > > To specify a peer name and region, first look up available regions by running `fly platform regions`. Select a region with a check mark in the Gateway column. > > Then run: `fly wireguard create [your-org] [region] [peer-name]` #### 3. Import your tunnel ##### Windows Run the WireGuard app. Click **Import tunnel(s) from file**. Select your configuration file. The WireGuard app will display the details of your tunnel. Click **Activate** to bring the tunnel online. ##### macOS Run the WireGuard app. Click **Import tunnel(s) from file**. Select your configuration file and click **Import**. You might be prompted by the OS that WireGuard would like to add VPN configurations; click **Allow**. The WireGuard app will display the details of your tunnel. Click **Activate** to bring the tunnel online. ##### Ubuntu Linux If you don't have `wg-quick` installed, then run the command below. For Ubuntu 18.04 to 22.04, `openresolv` is also required. ``` sudo apt install wireguard-tools openresolv ``` Copy the configuration file to `/etc/wireguard`; you'll need root/sudo permissions: ``` sudo cp basic.conf /etc/wireguard ``` Run `wg-quick` to bring `up` the connection by name (i.e. less the `.conf` extension). For example: ```bash cmd theme={null} wg-quick up mypeer ``` ```output output theme={null} [#] ip link add mypeer type wireguard [#] wg setconf mypeer /dev/fd/63 [#] ip -6 address add fdaa:0:4:a7b:ab6:0:a:102/120 dev mypeer [#] ip link set mtu 1420 up dev mypeer [#] resolvconf -a tun.mypeer -m 0 -x [#] ip -6 route add fdaa:0:4::/48 dev mypeer ``` ### Connect to the Fly.io DNS over WireGuard The DNS server address is different on WireGuard connections than on Machines. That's because you can run multiple WireGuard connections; your dev laptop could be WireGuard-connected to multiple organizations, but a Machine can't be. Your DNS server address for a WireGuard connection is part of the WireGuard tunnel configuration that flyctl generates. Your platform WireGuard tools might read and automatically configure DNS from that configuration, or they might not. Here's an example of a WireGuard configuration file generated by the `fly wireguard create` command: ```yaml theme={null} [Interface] PrivateKey = [redacted] Address = fdaa:0:18:a7b:d6b:0:a:2/120 DNS = fdaa:0:18::3 ``` You guessed it; it's the `DNS` line. Your DNS server address will also start with `fdaa:`, but the next two parts are unique to your organization's network. All 6PN addresses are prefixed by the organization's network ID; that's the part of the address that locks it to your organization. All our WireGuard DNS addresses follow this pattern: take the organization prefix, and tack `::3` onto the end. In this example, the WireGuard peer address is: ``` fdaa:0:18:a7b:d6b:0:a:2 ``` The 6PN prefix is the first 3 `:`-separated parts, so the DNS server address for this example is: ``` fdaa:0:18::3 ``` To use `dig` to probe DNS on a WireGuard connection, supply the DNS server address to it. For example: ```bash cmd theme={null} root@f066b83b:/# dig +short aaaa my-app-name.internal @fdaa:0:18::3 ``` ```output output theme={null} fdaa:0:18:a7b:7d:f066:b83b:2 ``` ### Test the VPN tunnel If you have the `dig` tool installed, a TXT query to `_apps.internal` will show all the apps in the organization you are connected to: ```bash cmd theme={null} dig +noall +answer _apps.internal txt ``` ```output output theme={null} _apps.internal. 5 IN TXT "my-app-name,my-app-name-0,my-app-name-1" ``` You can also query for peer names and addresses: ```bash cmd theme={null} dig +short txt _peer.internal @fdaa:0:18::3 ``` ```output output theme={null} "my-peer" ``` ```bash cmd theme={null} dig +short aaaa my-peer._peer.internal @fdaa:0:18::3 ``` ```output output theme={null} fdaa:0:18:a7b:7d:f066:b83b:102 ``` Query for the 6PN addresses of all started Machines in an app: ``` dig +short aaaa my-app-name.internal ``` ### Manage WireGuard on Fly.io #### List the tunnels To list all the tunnels set up for an organization, run `fly wireguard list`. You can provide an organization on the command line or select an org when prompted. #### Remove a tunnel To remove a tunnel, run `fly wireguard remove`. You can specify the organization and tunnel name on the command line or be prompted for both. ### Troubleshoot a WireGuard VPN connection Having trouble connecting to a Fly.io hosted app? When you can't connect to something, it's helpful to establish a baseline of what is, or what is not, working. #### Am I connected to Fly.io VPN? When connected locally, you can run a `dig` command to list all the apps your connection has access to. ```bash cmd theme={null} dig _apps.internal TXT +short ``` ```output output theme={null} my-app,my-app-db ``` If results are returned, you have a VPN connection to an org at Fly.io and the results list all the app names. If no results are returned, either you do not have a WireGuard VPN connection open or there are NO apps running in the org. #### Am I connected to the right Fly.io org? WireGuard connections are created to a specific org. Each org's network is isolated from other orgs. You may have a VPN connection to Fly.io, but it may be to a different org than where your app is located. By default, you are working with your **personal** organization. If the app is in a business focused org or a shared org, you need to ensure your VPN connection is the org where the app lives. You can also try to ping your app's Machine. Mac version: ```bash theme={null} ping6 my-app.internal ``` Linux version: ```bash theme={null} ping -6 my-app.internal ``` If the ping succeeds, you have a VPN connection to the same org that your application's Machine is running in. If the ping fails, check which org the application is deployed in. ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = my-app Owner = my-biz Hostname = my-app.fly.dev Image = my-app:deployment-0123456789 Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 90706e10f12094 10 ord started 2024-04-16T20:20:59Z ``` The `Owner` is the **organization**. In this example, it is `my-biz`. Then check that your WireGuard connection is to the same organization. To be certain, you can remove the current connection and re-create it explicitly specifying the org. # Request headers Source: https://docs.fly.io/networking/request-headers Read the request headers your app gets on Fly.io, like Fly-Client-IP, Fly-Region, X-Forwarded-For, X-Forwarded-Proto, and Via, with spoofing cautions. ![Illustration by Annie Ruygt of flying ghosts carrying lanterns](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/request-headers.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=1cd57f862573e5a01668c9146679e9b7) Request headers carry information that is specific to the incoming request and its path taken to the application. Request headers are added by Fly Proxy. ## Request Headers ### `Fly-Client-IP` **Client IP Address**: The IP address of the client from the perspective of Fly Proxy. If you are using another reverse proxy service in front of the Fly.io platform, this header will be set to proxy service's IP addresses, rather than the client making the request. In that case, you'll need to parse the IP addresses in the [`X-Forwarded-For` header](#x-forwarded-for) to find the client IP address. ### `Fly-Forwarded-Port` **Original connection port**: This header is always set by the Fly Proxy and denotes the actual port that the client connected to the Fly edge node which is then forwarded to the application instance. ### `Fly-Region` **Edge Node Region**: This header is a three letter region code which represents the region that the connection was accepted in and routed from. Not to be confused with the [environment variable](/machines/runtime-environment#fly_region) `FLY_REGION`, which is where the application is running. ### `X-Forwarded-For` **Client and Proxy List**: A comma separated list of IP addresses including the address of the client that originated the request and the addresses of the proxy servers the request passed through. For example, "77.97.0.98, 77.83.142.33" contains the client and the one proxy it passed through. On the Fly.io platform, the last address (rightmost) in this list will be a shared or dedicated IP address assigned to your app. This header must be treated with caution to avoid spoofing attempts. Follow the directives in the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) for `X-Forwarded-For` to learn how to parse the header safely. If you aren't using another reverse proxy in front of the Fly.io platform, the [`Fly-Client-IP` header](#fly-client-ip) might be a better choice to get client IP addresses. ### `X-Forwarded-Proto` **Original client protocol**: The protocol which the client used to make the request. Either `http` or `https`. ### `X-Forwarded-Port` **Original connection port**: This header may be set by the client and should denote the port that the client set out to connect to. ### `X-Forwarded-SSL` **SSL Status**: This indicates if the client connected over SSL. Its value can be either `on` or `off`. ## Request and Response Headers ### `Via` **Proxy Route**: This header, added by proxies, shows the path taken, and protocols used, by the connection. MDN has [full documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Via) for this header. For example, a connection through the Fly edge may show `2 fly.io` in the field, denoting that version 2 of HTTP was used by the connection as it passed through the Fly Proxy. # Public Network Services Source: https://docs.fly.io/networking/services Set up public networking for a Fly.io app: Anycast IPv6, shared and dedicated IPv4, HTTP, TLS and PROXY protocol handlers, HTTPS redirects and outbound IPs. ![Illustration by Annie Ruygt of a ship carrying apps](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/public_network_services.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=1ed9a8a0627b8b11fd15c76cf3b63089) Fly.io has public and private network services available. The public network services connect apps to the wider public internet, while the [private network services](/networking/private-networking) allow apps to communicate with other apps within the Fly.io private network. ## Anycast IP addresses IPv6 addresses and shared IPv4 Anycast addresses are free. Dedicated IPv4 addresses are [billed](/about/pricing#anycast-ip-addresses) monthly. > [!NOTE] > > Anycast IP addresses described on this page are **not used** for outbound connections made from within a Machine. See [Egress IP Addresses](/networking/egress-ips) for details. ### About Anycast We announce global IP blocks from all of our datacenters over BGP, otherwise known as Anycast. Anycast is a core internet routing mechanism that connects clients to the "nearest" server advertising a block of IPs. You can read [all about it on Wikipedia](https://en.wikipedia.org/wiki/Anycast). ### IPv6 A new Fly App running a public service configured in [`fly.toml`](/reference/configuration) automatically gets a dedicated Anycast IPv6 address when it's first deployed. > [!NOTE] > > If you create apps with public services in their Machine config by using [`fly machine` commands](/machines/flyctl/fly-machine-run#define-a-fly-proxy-network-service) or the [Machines API](/machines/api), then you need to run `fly ips allocate-v6` to get a dedicated IPv6 address. ### Shared IPv4 Along with the dedicated IPv6 address, apps are automatically given a shared Anycast IPv4 address on the first deployment when they're configured to handle one or both of the following protocols and ports in [`fly.toml`](/reference/configuration): * HTTP on port 80, or * TLS & HTTP on port 443 This includes apps configured with the simplified [`[http_service]`](/reference/configuration#the-http_service-section) section, which handles only these protocols and ports by default. You can use a shared IPv4 address for TCP ports other than 80 and 443 by using the [TLS handler](#tls-handler) when you specify the port. Shared IPv4 addresses are recommended unless you have an [explicit need for your own IP](#dedicated-ipv4). Shared IPv4 addresses are shared across many apps and organizations. Routing is based on your app's domain. To allocate a shared IPv4 to an app without a public IPv4 address, run: ```bash theme={null} fly ips allocate-v4 --shared ``` > [!NOTE] > > If you create apps with public services in their Machine config using [`fly machine` commands](/machines/flyctl/fly-machine-run#define-a-fly-proxy-network-service) or the [Machines API](/machines/api), then you need to run `fly ips allocate-v4 --shared` to get a shared IPv4 address. ### Dedicated IPv4 Dedicated IPv4 addresses are [billed](/about/pricing#anycast-ip-addresses) monthly. Shared IPv4 addresses are recommended unless you have an explicit need for your own IP. Some of the reasons to use a dedicated IPv4 address include: * Your app uses a non-HTTP protocol that doesn't use TLS. * Your app needs to do something over UDP (we don't support UDP over shared IPv4 or dedicated IPv6). * You want your app to accept raw TCP and handle TLS termination. * You have a Fly Postgres exposed to the internet over TLS for external connections. Allocating a dedicated IPv4 Anycast address is opt-in only. To manually allocate a dedicated IPv4 address for your app, run: ```bash theme={null} fly ips allocate-v4 ``` After allocating a dedicated IPv4 address, release the shared IPv4 address: ```bash theme={null} fly ips release ``` ### Switch from dedicated IPv4 to shared IPv4 The following steps should allow you to switch an app with a custom domain from a dedicated IPv4 address to a free shared IPv4 without downtime. 1. Add a shared IPv4 address with `flyctl ips allocate-v4 --shared`. 2. If you don't use a CNAME to `.fly.dev`, [add the shared IPv4 as an A record](/networking/custom-domain#a-and-aaaa-records-recommended). 3. Confirm your app works via shared IP: `curl -Iv http:// --resolve :80:`. You should receive a 301 redirect response. 4. If you are using a custom domain and don't already have an SSL certificate for this app, [create one](/networking/custom-domain#add-your-domain-to-your-app). The cert is required for routing to your app via the custom domain, even for HTTP connections. 5. Wait for DNS caches to clear. Five minutes is likely enough, but this varies wildly. This is determined by the larger of your DNS record’s TTL and that of our `.fly.dev` record. 6. Remove the dedicated IPv4 from the app using `fly ips release `. You can view your app's IP addresses using `fly ips list`. 7. Remove the unwanted IPv4 address from your DNS if it was set manually as an A record. If you do not have a custom domain (your app is accessed by its `.fly.dev` address), you don't have to change any DNS records yourself, and you don't need to add your own SSL cert. ## Connection handlers Handlers convert TCP connections into something your app can handle. Use handlers to specify which middleware applies to incoming TCP connections for each port you accept external connections on. ### TCP pass through If you don't specify handlers, we just forward TCP to your app as-is. This is useful if you want to handle TLS termination yourself, for example. ### Configure connection handlers Set the `handlers` config setting in the [`services.ports` section](/reference/configuration#services-ports) of your `fly.toml` file. Valid handler strings: * [`"http"`:](#http-handler) Convert TCP connection to HTTP * [`"tls"`](#tls-handler): Convert TLS connection to unencrypted TCP * [`"pg_tls"`](#postgres-tls-handler): Handle TLS for PostgreSQL connections * [`"proxy_proto"`](#proxy-protocol-handler): Wrap TCP connection in PROXY protocol You can also configure [TLS options](/reference/configuration#http_service-tls_options) for the `[http_service]` section, which has a TLS (and HTTP) handler by default on port 443. ### HTTP handler Many apps have limited HTTP support, the `http` handler normalizes HTTP connections and sends HTTP 1.1 requests to the application process. This is roughly how `nginx` and other reverse proxies work, and allows your app to globally accept modern HTTP protocols (like HTTP/2) without extra complexity. If your application needs HTTP/2 (like gRPC does), enable the `h2_backend` to talk directly over HTTP/2. Just make sure your app speaks `h2c` (that’s HTTP/2 without TLS), or it won’t work. The HTTP handler adds a number of standard HTTP headers to requests, and a few Fly.io-specific headers for convenience: | Header | Description | | - | - | | `Fly-Client-IP` | The IP address Fly.io accepted a connection from | | `X-Forwarded-For` | A comma separated list of proxy servers the request passed through. MDN has [full documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) for this header | | `X-Forwarded-Proto` | Original client protocol, either `http` or `https` | | `X-Forwarded-SSL` | Indicates if client connected over SSL, either `on` or `off` | | `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 specify a single region or multiple regions in order of preference: ``` 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: ``` Fly-Force-Instance-Id: instance-number ``` Configuration examples in `fly.toml`: ```toml theme={null} [[services]] ... [[services.ports]] handlers = ["http"] port = 80 force_https = true # optional ``` ```toml theme={null} [http_service] ... [http_service.http_options] h2_backend = true ``` ### TLS handler The `tls` handler terminates TLS using Fly.io-managed application certificates, then forwards a plaintext connection to the application process. This is useful for running TCP services and offloading `TLS` to the Fly Proxy. For performance purposes, the Fly Proxy will terminate TLS on the host a client connects to, and then forward the connection to the nearest available Machine. Configuration examples in `fly.toml`: ```toml theme={null} [[services]] ... [[services.ports]] handlers = ["tls"] port = 443 tls_options = { "alpn" = ["mqtt"], "versions" = ["TLSv1.2", "TLSv1.3"] } ``` ```toml theme={null} [http_service] ... [http_service.tls_options] alpn = ["h2", "http/1.1"] versions = ["TLSv1.2", "TLSv1.3"] default_self_signed = false ``` ### Postgres TLS handler The `pg_tls` handler manages Postgres connections, so that you can expose your Postgres databases over the proxy securely. Some interesting notes by @glebarez on why standard TLS termination won't work for Postgres: [https://github.com/glebarez/pgssl#readme](https://github.com/glebarez/pgssl#readme) Example configuration from default Fly Postgres app `fly.toml`: ```toml theme={null} [[services]] ... [[services.ports]] port = 5433 handlers = ["pg_tls"] ``` ### PROXY protocol handler The `proxy_proto` handler adds information about the original connection, including client IP + port and server IP + port (from the client's perspective). Most apps need additional logic to accept the PROXY protocol, either using a prebuilt library or implementing the [PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) directly. Fly.io supports version 1 (human-readable header format) of the PROXY protocol by default with the `proxy_proto` handler. You can configure the `proxy_proto` handler to use version 2 (binary header format) in the [`services.ports.proxy_proto_options`](/reference/configuration#services-ports-proxy_proto_options) section of your `fly.toml` file. Configuration examples in `fly.toml`: ```toml theme={null} [[services.ports]] handlers = ["proxy_proto"] port = 5000 proxy_proto_options = { version = "v2" } ``` ## HTTP to HTTPS redirects The `force_https` configuration option automatically redirects HTTP to HTTPS. When enabled, all HTTP requests return a redirect response with a `301` status code. This option can only be set on HTTP handlers - deployments will fail if set on other handlers. ## Outbound IP addresses Fly Machines have IPv6 addresses from which they make requests to the wider internet without going through the Fly Proxy. 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](/about/pricing#static-egress-ips-for-machines) monthly. 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 The Machine's outbound IPv6 address is available in the `FLY_PUBLIC_IP` [environment variable](/machines/runtime-environment). Use `fly ssh console -s` to get an interactive shell on the Machine of your choice. ```bash cmd theme={null} echo $FLY_PUBLIC_IP ``` ```out out theme={null} 2605:4c40:92:520a:0:8c93:3d8a:1 ``` You can also call out to an external service that'll tell you your IP. For example: ```bash cmd theme={null} curl text.ipv6.wtfismyip.com ``` ```out out theme={null} 2605:4c40:92:520a:0:8c93:3d8a:1 ``` or ```bash cmd theme={null} dig +short txt o-o.myaddr.l.google.com @ns1.google.com ``` ```out out theme={null} "2605:4c40:92:520a:0:8c93:3d8a:1" ``` So this Machine's outbound IPv6 address is `2605:4c40:92:520a:0:8c93:3d8a:1`. You may have to install software on the Machine to use `dig` or `curl`. On Debian, `dig` belongs to the `dnsutils` package. ## 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](/networking/app-services). You’re locked down by default. ## Related reading * [Connect to an App Service](/networking/app-services) How to expose your Fly App’s processes publicly or privately through services. * [Dynamic Request Routing with fly‑replay](/networking/dynamic-request-routing) How to route requests between regions, apps, or machines using Fly‑specific headers. * [Connect Your Private Network with WireGuard](/blueprints/connect-private-network-wireguard) Set up secure access between Fly.io and your private infrastructure using WireGuard tunnels. * [Private Networking](/networking/private-networking) How services communicate securely over Fly.io’s private network without going through the public internet. # TLS Support Source: https://docs.fly.io/networking/tls Fly Proxy accepts only TLSv1.2 and TLSv1.3. See the AES-GCM and ChaCha20-Poly1305 cipher suites it supports for each, plus SSL Labs results. The Fly proxy only supports TLSv1.2 and TLSv1.3 with strong ciphers. Learn more about our [built-in TLS termination](/security/tls-termination). ## Supported Cipher Suites These suites give us broad, secure coverage. We do not support very old browsers. **TLS 1.3:** ``` TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ``` **TLS 1.2:** ``` TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 ``` You can always get an updated list of supported ciphers from the [SSL Labs test results](https://www.ssllabs.com/ssltest/analyze.html?d=fly.io\&s=2a09%3a8280%3a1%3a0%3a0%3a0%3aa%3a791\&hideResults=on\&latest). # Running apps on UDP and TCP Source: https://docs.fly.io/networking/udp-and-tcp Run UDP and TCP apps on Fly.io: get a dedicated IPv4, bind UDP to fly-global-services on the same external port, mind MTU limits, and try a Go echo app. You can run any containerized app on our platform, regardless of what protocols it talks over, including apps that rely on UDP in addition to TCP. This guide walks you through how to set up your UDP app on Fly.io. Before getting started, here's the summary of important points about using UDP on Fly.io (that we'll expand on below): * You need a dedicated IPv4 address. You can't use a shared IPv4 address or an IPv6 address for UDP. We support public IPv6 and shared IPv4 for TCP. * The UDP side of your app needs to bind to the special `fly-global-services` address. But the TCP side of your app can't; it should bind to `0.0.0.0`. * The UDP side of your app needs to bind to the same port that is used externally. Fly will not rewrite the port; Fly only rewrites the IP address for UDP packets. * We swipe a couple dozen bytes from your MTU for UDP, which usually doesn't matter, but in rare cases might. ## You need a dedicated IPv4 address You'll need a [dedicated IPv4 address](/networking/services#dedicated-ipv4) for your app to accept UDP packets. We don't support UDP over public IPv6. Every Fly.io app gets a public IPv6 address and a shared IPv4 address that will work for the TCP side of your app. > [!NOTE] > > **Note:** UDP does work over [Fly.io IPv6 private networking](/networking/private-networking). ## Your app needs to bind to the `fly-global-services` address To receive UDP packets, your app needs to bind to the special `fly-global-services` address (that's it; that's the address). Just as importantly: it needs to reply to messages using that address. You usually need to explicitly bind your UDP service to `fly-global-services`. Sorry, but `0.0.0.0`, `*`, and `INADDR_ANY` generally won't do: Linux will use the wrong source address in replies if you use those. **But: TCP can't use `fly-global-services`** You can't bind your TCP service to `fly-global-services`; the TCP side of your app should probably bind to `*`. ### Why the `fly-global-services` address? For the most part, UDP "just works" on Fly.io, the way you'd expect it to. The thing about forwarding UDP that makes it different from HTTP is that UDP messages don't have HTTP headers. When a standard proxy receives an HTTP message and sends it to its target, it stashes the original source address in the headers, so the target knows who it's talking to. You can't generally do that with UDP messages. But you need to know who you're talking to in order to respond to a UDP message! We use ⚡️`eBPF magic`⚡️ to shuttle UDP packets across our forwarding fabric while transparently mapping source addresses. What that means for you is that you can generally just write a standard socket program that reads UDP messages and replies to them based on the source address of the packet. > [!NOTE] > > If we didn't do this `fly-global-services` thing, our UDP proxying logic would also try to proxy your app's own DNS traffic; it can't otherwise tell the difference between UDP packets you're sending to respond to a user and UDP packets your app generates in the ordinary course of its business. ## Your app needs to listen on the same port externally and internally Fly will only rewrite the IP addresses for UDP packets. The destination port for inbound packets will not be rewritten. That means if your app receives UDP traffic on port 5000 externally, the app needs to bind to `fly-global-services:5000` to receive that UDP traffic. ## You might need to be mindful of MTUs An invariant on almost every network is the maximum packet size, the MTU. Typically, the MTU is 1500 bytes. If you exceed 1500 bytes, your packets will fragment. [You don't want to fragment](https://datatracker.ietf.org/doc/html/draft-mathis-frag-harmful-00). Fly.io takes a bite out of your packets for two purposes: 1. We forward all traffic across a [WireGuard](https://www.wireguard.com/) mesh, and WireGuard eats 60 bytes of every packet. 2. The UDP proxy forwarding system we use grabs another dozen bytes to record the original addresses of the packets --- the source address we saw when your client's packet hit our edge. You don't see these bytes; they're stripped off the packet before it's delivered to your app. But they're used in transit. In most cases this won't matter to you at all. Modern UDP protocols are designed to assume they have far less than 1500 bytes to work with, because there are all sorts of weird links on the Internet with smaller MTUs. But if you're doing something super-custom, it's best to assume you've got something more like 1300 bytes to work with, not 1500. ## Example UDP app Let's build a simple echo service. You can play the home version at [this GitHub repository](https://github.com/fly-apps/udp-echo-). We'll listen on port 5000, UDP and TCP, and just bat back whatever clients send us. Use `flyctl launch` to create a `fly.toml` file. Use it to wire up the ports. ```toml theme={null} [[services]] internal_port = 5000 protocol = "udp" [[services.ports]] port = "5000" [[services]] internal_port = 5000 protocol = "tcp" [[services.ports]] port = "5000" ``` We're going to build this app in Go (don't worry, it's simple, you'll be able to follow it even if you aren't a gopher). It's going to be vanilla socket code. ### Let's See Some Code You light up UDP and TCP with standard socket code. Usually, the only fussy bit will be the `fly-global-services` bind for UDP. Here's what that looks like in Go: ```golang theme={null} func main() { // in other programming languages, this might look like: // s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) // s.bind("fly-global-services", port) udp, err := net.ListenPacket("udp", fmt.Sprintf("fly-global-services:%d", port)) if err != nil { log.Fatalf("can't listen on %d/udp: %s", port, err) } // in other programming languages, this might look like: // s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) // s.bind("0.0.0.0", port) // s.listen() tcp, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { log.Fatalf("can't listen on %d/tcp: %s", port, err) } go handleTCP(tcp) handleUDP(udp) } ``` The handlers for this app are textbook Go code; after the UDP bind, nothing else in your app will care about Fly.io. Here's TCP: ```golang theme={null} func handleTCP(l net.Listener) { for { conn, err := l.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { return } log.Printf("error accepting on %d/tcp: %s", port, err) continue } go handleConnection(conn) } } func handleConnection(c net.Conn) { defer c.Close() lines := bufio.NewReader(c) for { line, err := lines.ReadString('\n') if err != nil { return } c.Write([]byte(line)) } } ``` And here's UDP: ```golang theme={null} func handleUDP(c net.PacketConn) { packet := make([]byte, 2000) for { n, addr, err := c.ReadFrom(packet) if err != nil { if errors.Is(err, net.ErrClosed) { return } log.Printf("error reading on %d/udp: %s", port, err) continue } c.WriteTo(packet[:n], addr) } } ``` In a real Go app doing something like DNS, you might have a single thread reading messages off the UDP socket and passing them to a pool of workers over a buffered channel; you might not want to fork off a thread for every UDP message you receive. This is just an echo service, so we'll dispense with that detail. This program, [with a simple Dockerfile](https://github.com/fly-apps/udp-echo-/blob/main/Dockerfile) and the `fly.toml` configuration above, should just work on Fly.io. [Boot it up](/flyctl/cmd/fly_deploy) and throw some UDP packets at it with netcat: ```bash cmd theme={null} echo hello world | nc -w1 -u udp-echo-sample.fly.dev 5000 ``` ```output output theme={null} hello world ``` # Understanding Cloudflare Source: https://docs.fly.io/networking/understanding-cloudflare Use Cloudflare with Fly.io in DNS-only or orange cloud proxy mode, get certs via a _fly-ownership TXT record or Origin Certificate, and fix common issues. ![Illustration by Annie Ruygt of a big cloud with Fly balloons exiting from inside it](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/understanding-cloudfare.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=927075ced28ebcac9deac06dc1cc2f77) 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 means Fly.io can't use its default TLS-ALPN-01 certificate issuance process. The key to a smooth setup is a `_fly-ownership` TXT record that proves domain ownership to Fly.io and allows certificate validation to work through Cloudflare's proxy. > [!NOTE] > > **Important:** Wildcard certificates (e.g. `*.example.com`) cannot be issued automatically through Cloudflare's proxy, because Let's Encrypt requires a DNS-01 challenge for wildcards and Cloudflare's Universal SSL can interfere with it. To use a wildcard certificate behind Cloudflare's proxy, import a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate) instead. ### Recommended setup 1. Add your domain to your Fly app: ```bash theme={null} fly certs add example.com ``` 2. Add a `_fly-ownership` TXT record in Cloudflare DNS. Run `fly certs setup` to see the required record: ```bash theme={null} fly certs setup example.com ``` Add the TXT record shown in the output. This record proves domain ownership to Fly.io. 3. Configure your DNS records in Cloudflare: * Add an `A` record pointing to your Fly app's IPv4 address. * Add an `AAAA` record pointing to your Fly app's IPv6 address. * Alternatively, use a `CNAME` record pointing to your app's `.fly.dev` target. * Enable the Cloudflare proxy (orange cloud) for these records. 4. Set SSL mode in Cloudflare to **Full (strict)**. 5. Enable **Always Use HTTPS** in Cloudflare (recommended). Fly.io will automatically issue a Let's Encrypt certificate via the HTTP-01 challenge, which works through Cloudflare's proxy when the ownership TXT record is in place. Run `fly certs check example.com` to monitor validation progress. Some Cloudflare configurations will block HTTP-01 challenges altogether. If certificate issuance is not progressing, import a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate) instead. ### Using a Cloudflare Origin Certificate If automatic certificate issuance doesn't work for your setup, or if you need a wildcard certificate (like `*.example.com`), you can import a Cloudflare Origin Certificate instead. 1. Generate a Cloudflare Origin Certificate in the Cloudflare dashboard: * Go to SSL/TLS > Origin Server > Create Certificate. * Choose the hostnames to cover (for example, `example.com` and `*.example.com`). * Choose a validity period (Cloudflare offers up to 15 years). * Keep the page open — Cloudflare only shows the private key once. 2. Import the certificate to Fly.io in your app dashboard under **Certificates**. You can paste the certificate and private key directly from Cloudflare. Alternatively, save them as files and use the CLI: ```bash theme={null} fly certs import example.com --fullchain cert.pem --private-key key.pem fly certs import "*.example.com" --fullchain cert.pem --private-key key.pem ``` 3. Add the `_fly-ownership` TXT record shown in the certificate setup details, if you haven't already set one up. 4. Configure your DNS records and Cloudflare SSL mode as described in the [recommended setup](#recommended-setup) above. > [!NOTE] > > Custom and ACME certificates can coexist for the same hostname. The custom certificate is served as primary, and any ACME certificate acts as an automatic fallback. ## Common issues to watch for ### General issues * Always use "Full (strict)" in Cloudflare when connecting to Fly.io. Using "Flexible" mode may cause redirect loops. * Only one application should manage certificates for a subdomain or apex domain. Using more than one can cause conflicts. ### ACME-specific issues These issues apply when using Let's Encrypt (ACME) certificates behind Cloudflare's proxy. They do not apply if you are using a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate). * Check that your domain allows Let's Encrypt with a CAA record like: ``` example.com. CAA 0 issue "letsencrypt.org" ``` * Let's Encrypt has rate limits. Check the certificate status tab in the Fly.io dashboard if issuance fails. * DNS-01 certificates will conflict with Cloudflare Universal SSL. This happens when Cloudflare's Universal SSL automatically inserts hidden ACME challenge TXT records that don't appear in your DNS dashboard. These ghost records interfere with Let's Encrypt's validation process. The best choice in this situation is to import a [Cloudflare Origin Certificate](#using-a-cloudflare-origin-certificate) instead. ### Custom certificate issues * Unlike Let's Encrypt certificates, which renew automatically, custom certificates must be manually renewed and re-imported before they expire. Cloudflare Origin Certificates can be issued with long validity periods (up to 15 years), but you should still track expiry. ## Tools for debugging These tools can help when diagnosing certificate or DNS issues: * `fly certs check `: Check ownership TXT status, custom certificate status, and ACME certificate status all in one place. This is the best starting point for debugging. * [crt.sh](https://crt.sh/): Check issued certificates. * [DNSChecker](https://dnschecker.org/): Confirm DNS propagation. * [Let's Debug](https://letsdebug.net/): Analyze Let's Encrypt certificate validation issues. * dig: Inspect DNS records from the command line. For example: ```bash theme={null} fly certs check your-app.example.com dig AAAA your-app.example.com dig TXT _fly-ownership.your-app.example.com ``` ## Related topics * [Custom domain guide](/networking/custom-domain) * [Certificates API reference](/machines/api/certificates-resource) * [flyctl certs commands](/flyctl/cmd/fly_certs) * [fly certs import reference](/flyctl/cmd/fly_certs_import) # WebSockets Source: https://docs.fly.io/networking/websockets Run WebSocket apps on Fly.io. Fly Proxy keeps idle connections open, and closes them cleanly with code 1012 when it restarts or when autostop stops a Machine. WebSockets work on Fly.io without extra configuration. An app with an [`[http_service]`](/reference/configuration#the-http_service-section) section accepts `ws://` and `wss://` connections, and Fly Proxy passes the upgraded connection through to your app. ## Idle connections stay open Fly Proxy doesn't close idle WebSocket connections. A connection with no traffic stays open until your app or the client closes it. A connection can still close for reasons outside your app: * **Fly Proxy restarts.** When we deploy a new version of the proxy, it closes open WebSockets with a [close frame](#close-codes). * **Autostop stops the Machine.** See [WebSockets and autostop](#websockets-and-autostop). * **You deploy your app.** Each Machine restarts during a deploy, so its connections close. * **Something else on the network drops it.** Corporate proxies, load balancers and mobile networks often drop connections that have been idle for a while. So every WebSocket client should reconnect when its connection closes, and long-lived connections should send a heartbeat. ## Close codes When Fly Proxy closes a WebSocket because it's restarting or because the Machine is stopping, it waits for the current message to finish and then sends a close frame with the reason `restarting`: | Side | Close code | | - | - | | Client | `1012` (Service Restart) | | Your app | `1001` (Going Away) | Treat `1012` as a signal to reconnect. The new connection goes to a running Machine. ## Send a heartbeat A heartbeat is a small message sent at a regular interval, usually a WebSocket ping that the other side answers with a pong. It keeps intermediate networks from treating the connection as idle, and it lets each side notice when the other has gone away without closing the connection. Send one every 30 seconds or so. Some libraries do this for you. **Phoenix Channels:** The `phoenix` JavaScript client sends a heartbeat every 30 seconds and reconnects with backoff when the connection drops. You don't need to change anything. To change the interval, set `heartbeatIntervalMs`: ```js theme={null} import { Socket } from "phoenix"; const socket = new Socket("/socket", { heartbeatIntervalMs: 30000 }); socket.connect(); ``` **Socket.IO:** The server pings each client every 25 seconds (`pingInterval`) and drops clients that don't answer within 20 seconds (`pingTimeout`). The client reconnects automatically. These are the defaults: ```js theme={null} import { Server } from "socket.io"; const io = new Server(httpServer, { pingInterval: 25000, pingTimeout: 20000 }); ``` **`ws` for Node.js:** The [`ws`](https://github.com/websockets/ws) library has no built-in heartbeat. Ping each client on an interval and drop the ones that didn't answer the last ping: ```js theme={null} import { WebSocketServer } from "ws"; const wss = new WebSocketServer({ port: 8080 }); wss.on("connection", (ws) => { ws.isAlive = true; ws.on("pong", () => { ws.isAlive = true; }); }); const interval = setInterval(() => { for (const ws of wss.clients) { if (!ws.isAlive) { ws.terminate(); continue; } ws.isAlive = false; ws.ping(); } }, 30000); wss.on("close", () => clearInterval(interval)); ``` Browsers answer pings automatically. In the browser, reconnect when the connection closes. Add a random delay so that clients don't all reconnect at once after a restart: ```js theme={null} function connect() { const ws = new WebSocket("wss://my-app.fly.dev/socket"); ws.addEventListener("close", (event) => { // 1012 means Fly Proxy is restarting or the Machine is stopping const delay = event.code === 1012 ? Math.random() * 1000 : 1000 + Math.random() * 4000; setTimeout(connect, delay); }); return ws; } connect(); ``` ## WebSockets and autostop With [autostop](/launch/autostop-autostart), Fly Proxy stops a Machine when the app's other Machines can handle the current load. Open WebSockets count toward that load, but they don't keep a Machine running: the proxy spreads connections evenly across Machines, so the Machine it stops usually has some open. Before it stops a Machine, the proxy stops sending it new traffic and closes its open WebSockets with code `1012`. Clients that reconnect are routed to a Machine that's still running. Raw TCP services work the same way: autostop can close their open connections too. If you use autostop, make sure your clients reconnect. To keep one Machine running at all times in your primary region, set `min_machines_running`: ```toml theme={null} [http_service] internal_port = 8080 force_https = true auto_stop_machines = "stop" auto_start_machines = true min_machines_running = 1 ``` If your connections must not be interrupted by autostop, turn it off with `auto_stop_machines = "off"`. Connections can still close during deploys and proxy restarts. ## Related topics * [Autostop/autostart Machines](/launch/autostop-autostart) * [Guidelines for concurrency settings](/apps/concurrency) * [Fly Proxy autostop/autostart](/reference/fly-proxy-autostop-autostart) # Connect Your Client Source: https://docs.fly.io/postgres/client-configuration Tune clients for Fly.io Managed Postgres: pooled vs direct URLs, 600s connection lifetime, PgBouncer transaction mode, and pool settings per language. This guide covers how to connect your application to Fly Managed Postgres and configure your database client for reliable, performant connections. ## Quick start After [creating your MPG cluster](/postgres/create-and-connect) and attaching your app, these are the essentials: **1. Your connection string.** When you attach an app with `fly mpg attach`, Fly sets a `DATABASE_URL` secret on your app automatically. You can customize the variable name during attachment. Your app receives this as an environment variable at runtime. Both pooled and direct URLs are available from the **Connect** tab in your cluster's dashboard. * **Pooled URL** (default): `postgresql://fly-user:YOUR_PASSWORD@pgbouncer.YOUR_CLUSTER.flympg.net/fly-db` — routes through PgBouncer. Use this for your application. * **Direct URL**: `postgresql://fly-user:YOUR_PASSWORD@direct.YOUR_CLUSTER.flympg.net/fly-db` — bypasses PgBouncer. Use this for migrations, advisory locks, or `LISTEN/NOTIFY`. SSL is enabled by default on all MPG connections. You do not need to set `sslmode` in your connection string. **2. Set connection lifetime and idle timeout in your code.** These are settings you configure in your application's database client or connection pool library — not on the database or cluster side. Not all client libraries support these settings directly — see the [language-specific examples](#language-specific-configuration) below. | Setting | Recommended value | Why | | - | - | - | | Max connection lifetime | **600 seconds** (10 min) | Recycle connections before the proxy closes them | | Idle connection timeout | **300 seconds** (5 min) | Releases unused connections before they're forcibly closed | **3. Set up a direct URL for migrations.** Most frameworks run migrations on deploy. Migrations use advisory locks and other session-scoped features that require the direct URL, not the pooled one. ```bash theme={null} fly secrets set \ DATABASE_URL="postgresql://...@pgbouncer.YOUR_CLUSTER.flympg.net/fly-db" \ DIRECT_DATABASE_URL="postgresql://...@direct.YOUR_CLUSTER.flympg.net/fly-db" ``` ```toml theme={null} # fly.toml [deploy] release_command = "/bin/sh -lc 'DATABASE_URL=$DIRECT_DATABASE_URL bin/migrate'" ``` See the [Phoenix guide](/postgres/guides-examples/phoenix-guide) for Elixir-specific migration setup. **4. Disable prepared statements in transaction mode.** If your PgBouncer pool mode is set to **Transaction** (required for Ecto; recommended for high-throughput apps), you must disable named prepared statements in your client. PgBouncer can't track prepared statements across transactions. See [Cluster Configuration](/postgres/cluster-configuration) for how to change your pool mode. *** ## Why client configuration matters Your Fly.io apps, as well as your Fly.io Postgres databases, sit behind Fly.io's proxy. Public and private traffic route through this proxy. Sometimes our proxy restarts and the proxy does its best to drain connections before restarting. Postgres doesn't have a protocol level mechanism to tell clients to stop sending queries on a particular connection, so we have to rely on client-side configuration to handle graceful handoff. The proxy's shutdown timeout is **10 minutes**. Any connection that remains open after that is terminated. If your application holds connections for longer than this — which is the default behavior of most connection pools — you might run into errors like `tcp recv (idle): closed` or `ECONNRESET` during proxy deployments. The fix is straightforward: configure your connection pool to **proactively recycle connections** on a shorter interval than the proxy's timeout. ## PgBouncer mode and your client All MPG clusters include PgBouncer for connection pooling. The pool mode you choose on the cluster side affects what your client can do. See [Cluster Configuration](/postgres/cluster-configuration) for how to change modes. **Session mode** (default): A PgBouncer connection is held for the entire client session. Full PostgreSQL feature compatibility — prepared statements, advisory locks, `LISTEN/NOTIFY`, and multi-statement transactions all work normally. Lower connection reuse. **Transaction mode**: PgBouncer assigns a connection per transaction and returns it to the pool afterward. Higher throughput and connection reuse, but: * **Named prepared statements** don't work — you must use unnamed/extended query protocol * **Advisory locks** are not session-scoped — use the direct URL for migrations * **`LISTEN/NOTIFY`** doesn't work — use an alternative notifier (see the [Phoenix guide](/postgres/guides-examples/phoenix-guide) for Oban examples) * **`SET` commands** affect only the current transaction If your ORM or driver supports it, transaction mode with unnamed prepared statements is the better choice for most web applications. ## Language-specific configuration > **Node.js — pg (node-postgres)** > > ```javascript theme={null} > const { Pool } = require('pg'); > > const pool = new Pool({ > connectionString: process.env.DATABASE_URL, > > // Pool sizing > max: 10, > > // Connection lifecycle > idleTimeoutMillis: 300_000, // 5 min — close idle connections > maxLifetimeSeconds: 600, // 10 min — recycle before proxy timeout > connectionTimeoutMillis: 5_000, // 5s — fail fast on connection attempts > }); > ``` > > `maxLifetimeSeconds` was added in `pg-pool` 3.5.1 (included with `pg` 8.8+). If you're on an older version, upgrade — this setting is critical for reliable connections on Fly. > **Node.js — Prisma** > > Add the following query parameters to your connection string: > > | Parameter | Value | Purpose | > | - | - | - | > | `pgbouncer` | `true` | Disables prepared statements for PgBouncer compatibility | > | `connection_limit` | `10` | Pool size per Prisma client instance | > | `pool_timeout` | `30` | Seconds to wait for a connection | > > In your Prisma schema: > > ```prisma theme={null} > datasource db { > provider = "postgresql" > url = env("DATABASE_URL") > } > ``` > > Prisma manages its own connection pool internally. The `connection_limit` parameter controls the pool size per Prisma client instance. > **Python — SQLAlchemy** > > ```python theme={null} > import os > from sqlalchemy import create_engine > > engine = create_engine( > os.environ["DATABASE_URL"], > > # Pool sizing > pool_size=10, > max_overflow=5, > > # Connection lifecycle > pool_recycle=600, # 10 min — recycle connections before proxy timeout > pool_timeout=30, # 30s — wait for available connection > pool_pre_ping=True, # verify connections before use > > # Disable prepared statements for PgBouncer transaction mode > # (uncomment if using transaction mode) > # connect_args={"prepare_threshold": None}, # for psycopg3 > # connect_args={"options": "-c plan_cache_mode=force_custom_plan"}, # alternative > ) > ``` > > `pool_recycle` is the max connection lifetime — SQLAlchemy will close and replace connections older than this value. > > `pool_pre_ping` issues a lightweight `SELECT 1` before each connection checkout. This adds a small round-trip but catches stale connections before your query fails. > **Python — psycopg3 connection pool** > > ```python theme={null} > import os > from psycopg_pool import ConnectionPool > > pool = ConnectionPool( > conninfo=os.environ["DATABASE_URL"], > > # Pool sizing > min_size=2, > max_size=10, > > # Connection lifecycle > max_lifetime=600, # 10 min, stays ahead of PgBouncer's 600s idle timeout > max_idle=300, # 5 min — close idle connections > reconnect_timeout=5, > > # Disable prepared statements for PgBouncer transaction mode > # (uncomment if using transaction mode) > # kwargs={"prepare_threshold": None}, > ) > ``` > **Go — database/sql with pgx** > > ```go theme={null} > import ( > "database/sql" > "os" > "time" > > _ "github.com/jackc/pgx/v5/stdlib" > ) > > // Open the connection pool. > db, err := sql.Open("pgx", os.Getenv("DATABASE_URL")) > if err != nil { > log.Fatal(err) > } > > // Pool sizing > db.SetMaxOpenConns(10) > db.SetMaxIdleConns(5) > > // Connection lifecycle > db.SetConnMaxLifetime(10 * time.Minute) // recycle before proxy timeout > db.SetConnMaxIdleTime(5 * time.Minute) // close idle connections > ``` > > Go's `database/sql` handles connection recycling natively. `SetConnMaxLifetime` is the key setting — it ensures no connection is reused beyond the specified duration. > > To disable prepared statements for PgBouncer transaction mode, use the `default_query_exec_mode` connection parameter: > > ```go theme={null} > connStr := os.Getenv("DATABASE_URL") + "?default_query_exec_mode=exec" > db, err := sql.Open("pgx", connStr) > ``` > **Ruby — ActiveRecord (Rails)** > > ```yaml theme={null} > # config/database.yml > production: > url: ENV["DATABASE_URL"] > pool: ENV.fetch("RAILS_MAX_THREADS", 5) > idle_timeout: 300 # 5 min — close idle connections > checkout_timeout: 5 > prepared_statements: false # required for PgBouncer transaction mode > ``` > > For connection max lifetime, Rails 8.1+ supports `max_age` natively: > > ```yaml theme={null} > # config/database.yml (Rails 8.1+) > production: > url: ENV["DATABASE_URL"] > pool: ENV.fetch("RAILS_MAX_THREADS", 5) > max_age: 600 # 10 min — recycle before proxy timeout > idle_timeout: 300 > checkout_timeout: 5 > prepared_statements: false > ``` > > On older Rails versions, there is no built-in max connection age. Connections will still be recycled by the idle timeout if your app has enough traffic, but long-lived busy connections won't be recycled until they encounter an error. > **Elixir/Phoenix — Ecto** > > ```elixir theme={null} > # config/runtime.exs > config :my_app, MyApp.Repo, > url: System.fetch_env!("DATABASE_URL"), > pool_size: 8, > max_lifetime: 540_000..600_000, # max connection lifetime; the range staggers reconnects > prepare: :unnamed # required for PgBouncer transaction mode > ``` > > For comprehensive Phoenix setup including migrations, Oban configuration, and Ecto-specific troubleshooting, see the [Phoenix with Managed Postgres](/postgres/guides-examples/phoenix-guide) guide. > > > [!NOTE] > > > > **Note on connection lifetime in Ecto:** `:max_lifetime` requires DBConnection 2.10.0 or later (2.10.1+ if your connections sit idle). It takes a range in milliseconds rather than a single value, so reconnects are spread out instead of the whole pool expiring at once. MPG's PgBouncer closes client connections after 600s idle (`client_idle_timeout`) and retires its own server connections after 600s (`server_lifetime`). Recycling on a 9-10 minute schedule keeps your pool ahead of both, so connections are replaced on your terms instead of turning up closed mid-query. ## Connection limits Each MPG plan has a fixed number of PgBouncer connection slots shared across all clients. If your total pool size (across all app processes) exceeds this limit, new connections will be queued or rejected. | Plan | Max client connections | Max database connections | Reserve pool | | - | - | - | - | | Basic | 200 | 50 | 10 | | Starter | 200 | 50 | 10 | | Launch | 500 | 150 | 30 | | Scale | 1000 | 300 | 50 | | Performance | 1000 | 300 | 50 | **Max client connections** is the total number of client connections PgBouncer will accept. **Max database connections** is the number of actual connections PgBouncer opens to PostgreSQL. The reserve pool handles bursts above the normal pool size. ### Common connection limit errors **`FATAL: too many connections for role`** or **`remaining connection slots are reserved for roles with the SUPERUSER attribute`**: Your total pool size across all processes exceeds the PgBouncer connection limit. To fix: * Reduce `pool_size` / `max` in each process * Switch to **transaction** pool mode for better connection reuse * Check for connection leaks (connections opened but never returned to the pool) **Calculating your total pool usage:** If you have 3 web processes with `pool_size: 10` and 2 worker processes with `pool_size: 5`, your total is `(3 × 10) + (2 × 5) = 40` connections. ## Troubleshooting ### `tcp recv (idle): closed` or `tcp recv (idle): timeout` **Cause:** PgBouncer or the proxy closed an idle connection. MPG's PgBouncer closes client connections after 600s idle (`client_idle_timeout`). Proxy deployments also drain connections when old instances shut down. **Fix:** Set your client's idle timeout to **300 seconds** (5 min) and max connection lifetime to **600 seconds** (10 min), which keeps your pool ahead of PgBouncer's own idle timeout. Most connection pools reconnect automatically when a connection is closed, so these errors are transient. If you see them often, reduce your pool size so fewer connections sit idle. ### `ECONNRESET` or "connection reset by peer" **Cause:** A long-lived connection was terminated by something upstream, such as a proxy restart draining its remaining connections. **Fix:** Set max connection lifetime to **600 seconds** (10 min) or less, so your pool recycles connections on its own schedule. Enable retry logic with backoff for transient failures. ### Prepared statement errors Errors like `prepared statement "..." does not exist`, `prepared statement "..." already exists`, or `protocol_violation` on login all point to the same root cause: your client is sending named prepared statements through PgBouncer in transaction mode. PgBouncer assigns a different backend connection per transaction, so prepared statements created on one connection aren't available on the next. **Fix:** Disable named prepared statements in your client configuration: * **Node.js (pg):** This is the default behavior — no change needed * **Prisma:** Add `?pgbouncer=true` to your connection string * **Python (psycopg3):** Set `prepare_threshold=None` * **Go (pgx):** Use `default_query_exec_mode=exec` * **Ruby (ActiveRecord):** Set `prepared_statements: false` * **Elixir (Ecto):** Set `prepare: :unnamed` ### Connection hangs on startup **Cause:** DNS resolution failure on Fly's internal IPv6 network. Your app can't resolve the `.flympg.net` address. **Fix:** Ensure your app is configured for IPv6. For Elixir apps, see the [IPv6 settings guide](/elixir/getting-started#important-ipv6-settings). For other runtimes, verify that your DNS resolver supports AAAA records on the Fly private network. # Cluster configuration options Source: https://docs.fly.io/postgres/cluster-configuration Set PgBouncer to Session or Transaction pool mode, change your MPG plan, and add databases and users with Schema Admin, Writer, or Reader roles. ![Illustration by Annie Ruygt of a balloon doing a lot of tasks](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Managed_Postgres.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8bc520d714c11065db621817396c13d6) ## 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. For configuring your application's connection pool settings (lifetime, idle timeout, pool size, and language-specific examples), see [Connect Your Client](/postgres/client-configuration). ### 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. > [!WARNING] > > 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 or through `flyctl`. 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 via the dashboard: 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. To create additional users via `flyctl`: 1. Run `fly mpg users create` to create a new user for your MPG cluster 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. ## Databases Your Managed Postgres cluster is created with the default `fly-db` database. You can create additional databases from the dashboard or through `flyctl`. These databases can be accessed by existing users in your cluster, based on their role. ### Creating additional Databases To create additional databases via the dashboard: 1. Navigate to your MPG cluster's "Databases" tab in the dashboard 2. Enter a name for your new database in the input field under the "Databases" section. 3. Click "Create database" and wait for the database to be created. To create additional database via `flyctl`: 1. Run `fly mpg databases create` to create a new database for your MPG cluster. # Create and Connect to a Managed Postgres Cluster Source: https://docs.fly.io/postgres/create-and-connect Create a Managed Postgres cluster with fly mpg create or the dashboard, attach an app with fly mpg attach, and connect locally via mpg proxy or WireGuard. ![Illustration by Annie Ruygt of a balloon doing a lot of tasks](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Managed_Postgres.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8bc520d714c11065db621817396c13d6) ## 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](/reference/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 PostGIS support. Other extensions, including pgvector, are enabled after creation from the [Extensions page](/postgres/extensions) ![A screenshot of the Managed Postgres creation page.](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/create-mpg.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=b42b842adf29b95412f3e92ae3d5d900) 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 ![Illustration by Annie Ruygt of a balloon holding a box of honey clusters cereal](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/create-cluster.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=4590d73a9557ea7000f41cfae0e6e862) You can also create an MPG cluster using the flyctl command line tool. To begin, run: ```bash theme={null} fly mpg create ``` Follow the prompts to configure your cluster name, plan, and region. By default your cluster will be created with a 10GB initial volume, which grows automatically with your data; you can start larger using the `--volume-size` flag: ```bash cmd theme={null} fly mpg create [flags] ``` ```out out theme={null} -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 (basic, launch, scale) -r, --region string The target region --volume-size int Initial 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: ```bash theme={null} 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: ```bash theme={null} 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](/networking/private-networking) ### Connecting with psql To connect directly to your Managed Postgres database using psql: ```bash theme={null} 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](/flyctl/cmd/fly_mpg_proxy)' command: ```bash theme={null} fly mpg proxy [flags] ``` This will open a connection on one of your local ports that forwards to your database. ```out theme={null} $ 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 theme={null} 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](/blueprints/connect-private-network-wireguard). Once connected, you can use the direct connection string from your MPG dashboard: ```bash theme={null} 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. # Supported Postgres Extensions Source: https://docs.fly.io/postgres/extensions Fly.io Managed Postgres runs Postgres 16 and 17. Enable extensions per database, including pgvector and PostGIS, and check the bundled extension list. ![Illustration by Annie Ruygt of a balloon doing a lot of tasks](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Managed_Postgres.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8bc520d714c11065db621817396c13d6) ## Overview Fly.io Managed Postgres (MPG) supports Postgres 16 and 17. Clusters include extensions for the Postgres version they run, including 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: ```bash theme={null} 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 The following table lists bundled extensions. Availability and extension versions depend on your cluster's Postgres version and configuration; check your cluster's Extensions page for the available extensions. Not all bundled extensions 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 | # Phoenix with Managed Postgres Source: https://docs.fly.io/postgres/guides-examples/phoenix-guide Run Phoenix on Fly.io Managed Postgres: PgBouncer Transaction mode with prepare: :unnamed, Ecto migrations on the direct URL, and Oban notifier options. ![Illustration by Annie Ruygt of a Phoenix bird resting with Frankie the balloon looking on](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/phoenix.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=d17161360b3e4be71cb7b1ee013bf651) For general connection configuration that applies to all languages — connection lifetime, idle timeouts, proxy restart behavior, and troubleshooting — see [Connect Your Client](/postgres/client-configuration). 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 theme={null} config :my_app, MyApp.Repo, url: System.fetch_env!("DATABASE_URL"), pool_size: 8, timeout: 15_000, # query timeout max_lifetime: 540_000..600_000, # max connection lifetime; the range staggers reconnects 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 theme={null} fly secrets set \ DATABASE_URL="postgresql://...@pgbouncer.YOUR_CLUSTER.flympg.net/fly-db" \ DIRECT_DATABASE_URL="postgresql://...@direct.YOUR_CLUSTER.flympg.net/fly-db" ``` In your fly.toml, update your release command to use the direct connection for running your migration: ```toml theme={null} [deploy] release_command = "/bin/sh -lc 'DATABASE_URL=$DIRECT_DATABASE_URL bin/migrate'" ``` Or run a migration manually: ```bash theme={null} 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 theme={null} # Distributed Erlang notifier config :my_app, Oban, repo: MyApp.ObanRepo, notifier: Oban.Notifiers.PG, queues: [default: 10] ``` Or with Phoenix PubSub: ```elixir theme={null} 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 theme={null} 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. ### 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 occur when the Fly proxy or PgBouncer closes an idle connection, often during routine proxy deployments. Ecto reconnects automatically, so these are transient. To reduce their frequency, lower your pool size so fewer connections sit idle. For a full explanation of why this happens and how to configure connection lifetime and idle timeouts, see [Client-Side Connection Configuration — Troubleshooting](/postgres/client-configuration#troubleshooting). * `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](/elixir/getting-started#important-ipv6-settings) # Import data from another postgres cluster Source: https://docs.fly.io/postgres/import Import data into Fly.io Managed Postgres from Fly Postgres or another provider using pg_dump and psql over fly mpg proxy, plus size limits and pgloader. ![Illustration by Annie Ruygt of a balloon doing a lot of tasks](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Managed_Postgres.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8bc520d714c11065db621817396c13d6) ## 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 : ```bash theme={null} 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] > > **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 theme={null} $ 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 theme={null} Connection String: postgres://fly-user:@localhost:16380/fly-db ``` 4. Run the dump and restore command ```bash theme={null} 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](/postgres/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. # Managed Postgres Source: https://docs.fly.io/postgres/index Get an overview of Fly.io Managed Postgres: backups, high availability, pgvector and PostGIS, storage, regions, features in development, and plan pricing. ![Illustration by Annie Ruygt of a balloon doing a lot of tasks](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Managed_Postgres.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8bc520d714c11065db621817396c13d6) ## 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](/networking/private-networking) * Multiple databases and schemas on that cluster * Programmatic access via the [Managed Postgres API](/machines/api), part of the Fly Machines API * Fly.io Support Portal to log tickets and get help * [Supported Postgres extensions](/postgres/extensions) for your cluster's Postgres version * 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 ### Current limitations * **Major version upgrades:** You choose Postgres 16 or 17 when you create a cluster, and you can't upgrade a cluster in place. To move to a newer major version, create a new cluster and copy your data with `pg_dump` and `pg_restore`. See [Import data](/postgres/import). * **Minor versions and security patches:** New Postgres images include the latest minor release. We roll out new images during your cluster's [maintenance window](/postgres/maintenance-windows), but not on a fixed schedule. PgBouncer and PostGIS versions are pinned and change only when we update them. * **Third-party extensions:** `pgvector` and `PostGIS` are supported, along with the bundled Postgres extensions marked as available on the [Extensions page](/postgres/extensions). To request another extension, [contact support](/about/support). * **Alerting:** We email your organization when maintenance starts and finishes, but there are no alerts for metrics like CPU or storage. Use [metrics](/postgres/metrics) to watch your cluster. * **Migration tools:** There's no automated migration from another database. Use `pg_dump` and `pg_restore`, as described in [Import data](/postgres/import). ## Regions See the [regions reference](/reference/regions) for where you can create a Managed Postgres cluster, or run `fly platform regions`. 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 storage your databases use 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 billed on usage, at **\$0.28 per GB for a 30-day month**. For example, if your databases use 10GB of storage, your monthly storage cost will be \$2.80. Usage is metered hourly, so a cluster that grows mid-month is charged for the larger size only from the hour it grows. Older v1 clusters, which have no **v2** badge next to the cluster name in the dashboard, are still billed on their provisioned storage rather than on usage. Their provisioned size is metered on the same hourly schedule, so resizing a v1 volume is prorated the same way. Clusters created or deleted mid-month will have their pricing prorated accordingly. Starting February 2026, inter-region private network usage will be charged at the [same rate](/about/pricing#data-transfer-pricing) as Machines. It will also share the same free quota as existing private network usage. There will be no charges for transfer within the same region. # Maintenance windows Source: https://docs.fly.io/postgres/maintenance-windows Choose when Fly.io runs routine maintenance on your Managed Postgres cluster, and what to expect when it does. ![Illustration by Annie Ruygt of a blue striped tiger curled up asleep beside a stacked database cylinder, with purple swirls and tall green leaves behind them](https://mintcdn.com/fly-io/D5JmKlchRF4d32O1/images/tiger-sleeping-beside-a-database-cylinder-2947.webp?fit=max\&auto=format\&n=D5JmKlchRF4d32O1\&q=85\&s=bf48bdc3ca5dfb5ffbb41951b0e04590) ## What's a maintenance window? Every Managed Postgres cluster has one maintenance window each week. The window has a day, a start time and a length. Routine maintenance that we schedule for your cluster only starts inside this window. That includes updating Postgres and the software that runs alongside it, and applying configuration changes. Pick a time when a short interruption won't bother anyone. Some things don't wait for the window. See [What can happen outside the window](#what-can-happen-outside-the-window). ## The default window New clusters start with this window: * **Day:** Sunday * **Start:** 1:00 AM * **Length:** 3 hours The window runs on your cluster's local time, not UTC. The local time comes from the cluster's region. For example, a cluster in `iad` uses `America/New_York`, so its default window is Sunday from 1:00 AM to 4:00 AM Eastern. You can't choose the time zone. The **Cluster local time** label on the Maintenance window card shows the time zone and its current UTC offset. | Region | Time zone | | - | - | | `ams` | Europe/Amsterdam | | `dfw`, `ord` | America/Chicago | | `fra` | Europe/Berlin | | `gru` | America/Sao\_Paulo | | `iad` | America/New\_York | | `lax`, `sjc` | America/Los\_Angeles | | `lhr` | Europe/London | | `nrt` | Asia/Tokyo | | `sin` | Asia/Singapore | | `syd` | Australia/Sydney | | `yyz` | America/Toronto | Regions that aren't listed here use UTC. The window follows daylight saving time, so it starts at the same local clock time all year. Two edge cases: * If your start time doesn't exist on the day the clocks spring forward, the window starts at the first valid time after the jump. * If your start time happens twice on the day the clocks fall back, the window uses the first one. ## Change your maintenance window 1. Open your cluster's **Settings** tab in the dashboard. 2. On the **Maintenance window** card, choose a day under **Repeat on**, a start time under **From**, and a length under **Duration**. 3. Check the summary line. For example: "Sunday from 1:00 AM for 3 hours in America/New\_York (UTC-04:00)". 4. Click **Save maintenance window**. ![A screenshot of the Maintenance window card on a Managed Postgres cluster's Settings tab, set to Sunday from 1:00 AM for 3 hours in America/New\_York](https://mintcdn.com/fly-io/XSSUr_WsWb-FRQwe/images/mpg-maintenance-window-settings.webp?fit=max\&auto=format\&n=XSSUr_WsWb-FRQwe\&q=85\&s=cfb19f191e573b2ef7ead5bfa575d0e8) To go back to the default window, click **Reset to default**. The new window takes effect as soon as you save it. That includes maintenance that's already queued for your cluster. | Setting | Allowed values | | - | - | | Day | One day a week, Monday to Sunday | | Start time | Any whole hour, 12:00 AM to 11:00 PM | | Duration | 3 to 8 hours, in whole hours | **Note**: You can only change the window from the dashboard. `flyctl` can't change it yet, and you can't set it when you create a cluster. New clusters always start with the default window, and you can change it after the cluster is created. ## What happens during maintenance * **When it starts:** Maintenance doesn't always start the moment your window opens. It can start at any point while the window is open. If it can't start before the window closes, it waits for next week's window. * **When it ends:** If an update has already started, we let it finish, even if it runs past the end of your window. If more updates are queued behind it and the window has closed, they wait for your next window. * **How it runs:** We update the machines that need it, one at a time. This usually means the connection poolers first, then the replica. Next, we switch over so the updated replica becomes the primary, and we update the old primary last. Not every maintenance touches every machine. * **Impact on your app:** Expect a short interruption. Open connections drop, and writes can fail briefly while the primary changes over or restarts. Apps that retry and reconnect handle this well. For settings that help, see [Connect Your Client](/postgres/client-configuration). * **Emails:** We email your organization when maintenance starts, and again when it finishes successfully. These emails go to org members who have alerts turned on. You can turn alerts on or off for each member in the **Alerts** column of your organization's Members page. Admins get these emails unless they've turned alerts off. * **Dashboard:** While maintenance runs, your cluster pages show a **Maintenance in progress** banner. You can still view everything, but you can't make changes until it finishes. This covers changes to the plan, users, databases, backups, settings and the maintenance window. ## What can happen outside the window The window only covers routine maintenance that we schedule. These things don't wait for it: * **Changes you make.** Plan changes and pool mode changes take effect right away, and they can restart nodes or poolers when you make them. See [Cluster configuration options](/postgres/cluster-configuration). * **Automatic failover.** If your primary fails, your cluster fails over to the replica straight away. * **Fixes you ask for.** If you're working with support on a problem, we can apply a fix right away with your agreement, instead of waiting for your window. * **Urgent fixes.** We fix hardware failures, host problems and other urgent platform issues as they happen, not at your scheduled time. These can also drop connections. Platform incidents are posted at [status.flyio.net](https://status.flyio.net). ## FAQ **Can I skip or postpone a scheduled maintenance?** Not from the dashboard. To control when maintenance runs, move your window. If we've told you a fix is queued for your cluster and you want it sooner, you have two options. You can move your window earlier, then move it back after the maintenance-complete email arrives. Or you can contact support and ask us to apply the fix right away. **Does maintenance cause downtime?** Usually only briefly. Expect dropped connections and a short period where writes fail. Set your window for a quiet time and make sure your app reconnects. **Why can't I change my maintenance window right now?** You can't change the window while maintenance or another cluster operation is running. The form shows "Maintenance settings can be changed after the current cluster operation finishes." Try again once the operation is done. If the form stays locked for a long time, contact support. **What if I restore a cluster from a backup?** The restored cluster keeps the source cluster's maintenance window. # Monitoring and Metrics Source: https://docs.fly.io/postgres/metrics The Managed Postgres Metrics tab: connection and pooler tiles, CPU and memory charts, cache hit ratio, deadlocks, replication lag, and v2 Overview tiles. ![Illustration by Annie Ruygt of a balloon doing a lot of tasks](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/Managed_Postgres.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=8bc520d714c11065db621817396c13d6) ## 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 (5 minutes, 15 minutes, 1 hour, 6 hours, 24 hours, 2 days, or 7 days) On older v1 clusters, the Metrics tab may show a **New Metrics System Available** card instead of charts. Enabling the new metrics system restarts all Postgres nodes in your cluster to apply the monitoring configuration. On v2 clusters, if a database machine was recently throttled significantly, a **CPU throttling detected** or **Severe CPU throttling** banner appears at the top of the tab. ### Metrics tiles The top of the **Metrics** tab shows four tiles with the cluster's current state: | Tile | Description | | - | - | | **Database Connections** | Current connections to the selected database, against the cluster-wide `max_connections` setting | | **Pooled Connections** | Current number of clients on the PGBouncer pool, active and waiting combined | | **Longest Query** | Duration of the longest-running active query in the selected database | | **Replication Lag** | Current maximum replay lag. The same measurement as the Replication Delay Seconds chart, as a single current value | ### Available Charts and Metrics Below the tiles, each chart plots its series over the selected time range, with each series' average shown underneath. The charts are grouped into these categories: #### System Resource Metrics | Metric | Description | | - | - | | **Database CPU Utilization** | CPU usage percentage for each database instance, labeled with Primary (P) and Replica (R) badges. On v2 clusters this chart also shades periods when the database was CPU-throttled. A throttled machine can otherwise appear to sit at a flat 100% CPU | | **Database Memory Utilization** | Memory usage over time for Primary and Replica database instances, labeled with instance IDs | | **Pooler CPU Utilization** | CPU usage percentage for PGBouncer connection pooler instances. Typically remains low under normal operations | | **Pooler Memory Utilization** | Memory consumption for connection pooler instances. Shows usage for each pooler with instance IDs | #### Connection Metrics | Metric | Description | | - | - | | **Database Connections** | 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 into selects, inserts, updates, and deletes. Shows throughput patterns and workload distribution across operation types | | **Cache Hit Ratio** | Proportion of block accesses served from PostgreSQL's buffer cache. Reads outside that cache may still be served by the operating system's page cache. | | **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** | Database size over time. Use the database dropdown to view a specific database | #### 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 | ## Overview tiles (v2 clusters) The **Overview** page of a v2 cluster (marked **v2** next to the cluster name) shows summary tiles with the cluster's current state. v1 clusters do not have these tiles. | Tile | Description | | - | - | | **CPU Usage** | Current CPU use of the primary | | **Memory Usage** | Current memory use of the primary | | **Storage Usage** | Approximate storage consumed by user databases, shown against each node's allocated storage. Excludes WAL and system overhead, so it does not represent total volume usage or remaining capacity | | **Active Connections** | Open connections to your databases, including idle connections, against the cluster's `max_connections` setting | | **Uptime** | How long the cluster has continuously had a primary up, over the last 30 days | # Do More Source: https://docs.fly.io/python/do-more Extend a Python app on Fly.io with extra services: attach and connect a Postgres database, add S3-compatible object storage, and run Ollama behind an API. You got your python app up and running, now what? There's lots of appliances you can add to your python project, in these guides we explore how to set them up on the fly.io side as well as in your python app. ## Add Postgres How to setup and connect to a postgres database. ## Add Object Storage How to setup S3-compatible object storage and interact with it. ## Add Ollama How to setup ollama and interact with its API. # Add Object Storage Source: https://docs.fly.io/python/do-more/add-object-storage Add S3-compatible object storage to a Python app on Fly.io: create a Tigris bucket with flyctl, then list and use buckets from your code with boto3. ## Create a Tigris Storage Buckets ```bash cmd theme={null} fly storage create ``` ```output output theme={null} ? Select Organization: fly-ephemeral (fly-ephemeral) ? Choose a name, use the default, or leave blank to generate one: Your project (summer-grass-2004) is ready. Set one or more of the following secrets on your target app. BUCKET_NAME: summer-grass-2004 AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev AWS_ACCESS_KEY_ID: tid_xxxxxx AWS_SECRET_ACCESS_KEY: tsec_xxxxxx ``` ### Public buckets By default, buckets are private. If you need to serve public assets like images or JavaScript files, create a *public bucket*: ```bash theme={null} fly storage create --public ``` You can also make a public bucket private. ```bash theme={null} fly storage update mybucket --private ``` Objects in a public bucket can be accessed by anyone without authentication. Public content is served from dedicated domains using the bucket name as a subdomain: * `https://mybucket.t3.tigrisfiles.io/key-name` (primary) * `https://mybucket.t3.tigrisbucket.io/key-name` * `https://mybucket.t3.tigrisblob.io/key-name` No credentials or pre-signed URLs are needed. For production use, we recommend setting up a custom domain so your public URLs stay stable: ```bash theme={null} flyctl storage update mybucket --custom-domain assets.example.com ``` Then create a CNAME record for `assets.example.com` pointing to `mybucket.t3.tigrisbucket.io`. See the [Tigris Custom Domain docs](https://www.tigrisdata.com/docs/buckets/custom-domain/) for full setup instructions and the [Tigris Public Bucket docs](https://www.tigrisdata.com/docs/buckets/public-bucket/) for details on all available public domains. ### Object Level Access Control By default, all objects inherit the access control settings of the bucket they are in. If a bucket is private, all objects in it are also private and vice versa. However, you can make individual objects in a bucket public-read (or private) by setting an object level ACL on them. This lets you serve a public object from an otherwise private bucket, or a private object from an otherwise public bucket. For details on how to set this up see the [Tigris Object ACL docs](https://www.tigrisdata.com/docs/objects/acl/) To verify that everything has gone well, we can check whether the appropriate secrets have been set for our app: ```bash theme={null} fly secrets list ``` ## Connecting to the Bucket The de-facto library for interacting with s3 storage is [boto3](https://pypi.org/project/boto3/), let's add it to the project: ```bash theme={null} poetry add boto3 ``` Now we can initialize the client: ```python theme={null} import boto3 S3_URL = os.getenv("AWS_ENDPOINT_URL_S3") svc = boto3.client('s3', endpoint_url=S3_URL) ``` > [!NOTE] > > The AWS credentials will be automatically extracted from the environment. Let's plug that into our app: ```python theme={null} @app.get("/") async def read_root(): buckets = svc.list_buckets() return {"buckets": [bucket["Name"] for bucket in buckets["Buckets"]]} ``` At this point you can interact with the bucket through the `boto3` interface; refer to [the docs](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) for all the possibilities. When you re-deploy your app you should see a list of the buckets you have access to: ```bash theme={null} fly deploy ``` Take a look at [the gist](https://gist.github.com/fliepeltje/5be7d0a6f1ada057b1eb3aa357cd4961) of this setup to get a full picture. # Add Ollama Source: https://docs.fly.io/python/do-more/add-ollama Connect a Python app on Fly.io to an Ollama server over Flycast, set OLLAMA_HOST as a secret, and generate llama3.1 responses with the ollama AsyncClient. You can get an `ollama` image running on a GPU in a few minutes. Get started by adapting the following `fly.toml` file: ```toml theme={null} app = '' primary_region = 'ams' [build] image = 'ollama/ollama' [[mounts]] source = 'models' destination = '/root/.ollama' initial_size = '10gb' [http_service] internal_port = 11434 force_https = false auto_stop_machines = 'stop' auto_start_machines = true min_machines_running = 0 processes = ['app'] [[vm]] size = 'a100-80gb' ``` Modify the app name and region to suit your preferences and save the file as `fly.ollama.toml`. Then you can launch the app: ```bash theme={null} fly launch -c fly.ollama.toml --flycast ``` There are a couple of things to note here: * The `ollama` image is a GPU image, so you need to specify a GPU size in the `[[vm]]` section. * Not all regions have GPUs available, refer to region GPU availability for more info * The `[[mounts]]` section is used to mount a volume to store the models. This is a good practice to keep the models separate from the app code. * The volume size in this config file is 10gb, that's enough for small models. Change this value if you need more. * The `--flycast` flag creates a private IPv6 address for the app. Finally, this *only* starts the `ollama` server; at this point you cannot interact with any models yet. To do so, you will have to pull in a model with this one easy, short, intuitive command: ```bash theme={null} fly m run -e OLLAMA_HOST=http://.flycast --shell --command "ollama pull llama3.1" ollama/ollama ``` This command will pull in the `llama3.1` model. You can change the model name to suit your needs. At this point this model is now available to the internal network of the organization it is deployed in. You can access it using Flycast from this URL: `http://.flycast`. Now that we have a functioning `ollama` with a model, we have to expose the ollama host to our app. One way to do this is to set the host as a secret: ```bash theme={null} fly secrets set OLLAMA_HOST=http://.flycast ``` To interact with our new AI friend, we will have to install the `ollama` package: ```bash theme={null} poetry add ollama ``` Now we can initialize the client: ```python theme={null} import os from ollama import AsyncClient OLLAMA_HOST = os.getenv('OLLAMA_HOST') ollama_client = AsyncClient(OLLAMA_HOST) ``` From here we can start integrating it into our app: ```python theme={null} @app.get("/") async def read_root(): resp = await ollama_client.generate( model="llama3.1", prompt="Why is the sky not green?", ) return resp["response"] ``` When you re-deploy your app you should see llama's answer: ```bash theme={null} fly deploy ``` You can check out [this gist](https://gist.github.com/fliepeltje/8a8ce9e364097ce61db67cd713799b24) for the complete example app. # Add Postgres Source: https://docs.fly.io/python/do-more/add-postgres Connect a Python app on Fly.io to Fly Postgres with a DATABASE_URL secret, query it with asyncpg, and run a local Postgres in Docker for development. To create a Postgres cluster, use the `fly postgres create` command. The command will walk you through the creation with prompts for name, region, and VM resources. > [!NOTE] > > Because we keep adding shorter aliases, you can use any of the following and get the same result: `flyctl postgres`, `fly postgres`, `flyctl pg`, and `fly pg`. ```bash cmd theme={null} fly postgres create ``` ```output output theme={null} ? Choose an app name (leave blank to generate one): pg-test ? Select Organization: TestOrg (personal) ? Select region: Toronto, Canada (yyz) ``` During this process, you get to choose from several preset resource configurations for the app: ``` ? Select configuration: [Use arrows to move, type to filter] > Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk Production (High Availability) - 3 nodes, 2x shared CPUs, 4GB RAM, 40GB disk Production (High Availability) - 3 nodes, 4x shared CPUs, 8GB RAM, 80GB disk Specify custom configuration ``` The "Production" options give you a three-node cluster in a leader-replica configuration. A single-node "Development" instance can readily be scaled and [expanded to more regions](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication). If you select the "Development" single-node cluster configuration, then you can choose to scale down to zero if there are no open connections after one hour. ``` ? Scale single node pg to zero after one hour? (y/N) ``` > [!NOTE] > > You might need to configure any apps that connect to your Postgres app to scale to zero as well, otherwise your Postgres database will never have zero connections and will never scale down. Your app might also need to be able to wait for the database to start back up. Learn more about the [scale to zero feature](/unmanaged-postgres/managing/scale-to-zero), including how to turn it off. ``` Creating postgres cluster in organization TestOrg Creating app... Setting secrets on pg-test... Provisioning 1 of 1 machines with image flyio/postgres-flex:15.2 Waiting for machine to start... Machine 3287457df90185 is created ==> Monitoring health checks Waiting for 3287457df90185 to become healthy (started, 3/3) Postgres cluster pg-test created Username: postgres Password: 45V1YkwVDUzbkHj Hostname: pg-test.internal Flycast: fdaa:2:45b:0:1::7 Proxy port: 5432 Postgres port: 5433 Connection string: postgres://postgres:45V1YkwVDUzbkHj@pg-test.flycast:5432 Save your credentials in a secure place -- you won't be able to see them again! ``` > [!WARNING] > > Warning: Fly Postgres is not managed postgres. Read what that means [here](/unmanaged-postgres). ## Connecting to the Database To connect to our database, we first have to communicate with our app what the connection string is. Copy the value after "Connection string" in the output and then set a new secret: ```bash theme={null} fly secrets set DATABASE_URL=postgres://postgres:@.flycast:5432 ``` > [!NOTE] > > By default your postgres app is only available within your organization. Only apps within the fly organization are able to connect to it via the connection string above. Now we can access the `DATABASE_URL` from the environment: ```python theme={null} import os DATABASE_URL = os.getenv("DATABASE_URL") ``` At this point you can use a database driver to interact with the database. You have some options here: * [psycopg2](https://www.psycopg.org/docs/) / [psycopg3](https://www.psycopg.org/psycopg3/) * [asyncpg](https://pypi.org/project/asyncpg/) * [aiosql](https://pypi.org/project/aiosql/) * [sqlalchemy](https://www.sqlalchemy.org/) Let's create a function to get some metadata about the database using `asyncpg`: ```bash theme={null} poetry add asyncpg ``` ```python theme={null} import asyncpg async def get_db_meta() -> list[str]: conn = await asyncpg.connect(DATABASE_URL) records = await conn.fetch("select nspname from pg_namespace;") return [x["nspname"] for x in records] ``` Then we can add this to our app: ```python theme={null} @app.get("/") async def read_root(): names = await get_db_meta() return {"names": names} ``` Finally you can deploy the app to see it in action: ```bash theme={null} fly deploy ``` You can check out [this gist](https://gist.github.com/fliepeltje/5a2b63d4b1169f5cb200a665cee3f926) for the complete example app. ## Developing with Postgres Locally Often you will want to setup a local postgres to run against. One way to do this is to use `docker` and `direnv`. With the following command you can spin up a postgres database for your project: ```bash theme={null} docker run --name \ -e POSTGRES_DB= \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -p 5432:5432 \ -d postgres ``` Then in your project you can specify an `.envrc` file: ```bash theme={null} export DATABASE_URL="postgres://postgres:postgres@localhost:5432/" ``` If you run `direnv allow` the `DATABASE_URL` will be available to your app locally. # Python Framework Guides Source: https://docs.fly.io/python/frameworks Python framework guides for Fly.io, each deploying a simple app: FastAPI for type-hinted APIs, Flask for WSGI web apps, and Streamlit for data apps. Each one of the language guides will take you through creating and deploying a simple application. If you don't see a framework supported here that you'd like to run on Fly.io, [tell us in the Community](https://community.fly.io/). ## Deploy a FastAPI App FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. ## Deploy a Flask App Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to complex applications. ## Deploy a Streamlit App Streamlit turns data scripts into shareable web apps in minutes. All in pure Python. No front‑end experience required. # Deploy a FastAPI App Source: https://docs.fly.io/python/frameworks/fastapi Deploy a FastAPI app to Fly.io. fly launch detects FastAPI, generates a Dockerfile and fly.toml, and deploys your Python API to a Fly Machine. In this guide we’ll learn how to deploy a [FastAPI](https://fastapi.tiangolo.com/) application on Fly.io. FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. Deploying a FastAPI app on Fly.io is... well it's fast! You can be up and running in less than a minute. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Fast Api server on Fly.io is to use our FastAPI template: ```sh theme={null} git clone git@github.com:fly-apps/hello-fastapi.git fastapi-app cd fastapi-app fly launch --generate-name ``` ## *Deploy a FastAPI app from scratch* For managing our project, we use [Poetry](https://python-poetry.org/). For more information on the initial setup with poetry, refer to [setting up a python environment](/python/the-basics/initial-setup). We can initialize a new project like so: ```bash theme={null} poetry new fastapi-app cd fastapi-app ``` > [!NOTE] > > If you're using Poetry 2.0+ (the default when installed via `pipx`), the `poetry shell` command is no longer built in. Use `poetry run` to run commands (e.g., `poetry run python main.py`), or activate the environment with `poetry env activate`. See the [initial setup guide](/python/the-basics/initial-setup) for more details. Then we have to add the FastAPI dependency: ```bash theme={null} poetry add "fastapi[standard]" ``` Now, let's create a simple FastAPI app in `main.py`: ```python theme={null} from fastapi import FastAPI app = FastAPI() @app.get("/") async def hello_fly(): return 'hello from fly.io' ``` We can then serve the development version of the app using the `fastapi` cli tool: ```bash theme={null} fastapi dev ``` This will display a 'hello from fly.io!' message when you visit the root URL. With the development environment we also get cool features like hot-reloading. To run in production mode, we can amend the command: ```bash theme={null} fastapi run ``` > [!NOTE] > > The `fastapi` assumes your app is stored in `main.py` or `app.py` - if this is not the case, you must specify that when running the app: > > `fastapi run hello/entrypoint.py` And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a FastAPI app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/[app-name] We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [multi-stage builds](/python/the-basics/multi-stage-builds). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/fly-apps/) for a reference. # Deploy a Flask App Source: https://docs.fly.io/python/frameworks/flask Deploy a Flask app to Fly.io. fly launch detects Flask, generates a Dockerfile that serves it with Gunicorn, and deploys it to a Fly Machine. In this guide we’ll learn how to deploy a [Flask](https://flask.palletsprojects.com/) application on Fly.io. Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to complex applications. Spinning up a flask app is fun! ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Flask server on Fly.io is to use our Flask template: ```sh theme={null} git clone git@github.com:fly-apps/hello-flask-poetry.git flask-app cd flask-app fly launch --generate-name ``` ## *Deploy a Flask app from scratch* For managing our project, we use [Poetry](https://python-poetry.org/). For more information on the initial setup with poetry, refer to [setting up a python environment](/python/the-basics/initial-setup). We can initialize a new project like so: ```bash theme={null} poetry new flask-app cd flask-app ``` > [!NOTE] > > If you're using Poetry 2.0+ (the default when installed via `pipx`), the `poetry shell` command is no longer built in. Use `poetry run` to run commands (e.g., `poetry run python main.py`), or activate the environment with `poetry env activate`. See the [initial setup guide](/python/the-basics/initial-setup) for more details. To run this app we will need 2 dependencies: * flask: the framework * gunicorn: the server ```bash theme={null} poetry add flask gunicorn ``` Now let's create a basic app in `app.py`: ```python theme={null} from flask import Flask app = Flask(__name__) @app.route("/") def hello_fly(): return "hello from fly.io" ``` To check that everything is working, we can use the included flask cli tool: ```bash cmd theme={null} flask run ``` ```out out theme={null} * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 Press CTRL+C to quit ``` Note that `flask run` command works since our file is named `app.py`. It also works if your file is named `wsgi.py`, so you don't have to use [`--app`](https://flask.palletsprojects.com/en/latest/cli/) to tell Flask where your app is. More details [here](https://flask.palletsprojects.com/en/latest/quickstart/#a-minimal-application). If your file is saved as `hello.py` instead of `app.py`, you would need to use the `--app` option to point to Flask where your app is: ```bash theme={null} flask --app hello run ``` If you open [http://127.0.0.1:5000/](http://127.0.0.1:5000/) in your web browser, it should display `hello from fly.io`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Flask app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/[app-name] We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [multi-stage builds](/python/the-basics/multi-stage-builds). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/fly-apps/) for a reference. # Deploy a Streamlit App Source: https://docs.fly.io/python/frameworks/streamlit Deploy a Streamlit app to Fly.io. fly launch detects Streamlit, generates a Dockerfile and fly.toml, and deploys your data app to a Fly Machine. In this guide we’ll learn how to deploy a [Streamlit](https://streamlit.io/) application on Fly.io. Streamlit turns data scripts into shareable web apps in minutes. All in pure Python. No front‑end experience required. Spinning up a `streamlit` app takes no time at all! ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Streamlit server on Fly.io is to use our streamlit template: ```sh theme={null} git clone git@github.com:fly-apps/hello-streamlit.git streamlit-app cd streamlit-app fly launch --generate-name ``` ## *Deploy a Streamlit from scratch* For managing our project, we use [Poetry](https://python-poetry.org/). For more information on the initial setup with poetry, refer to [setting up a python environment](/python/the-basics/initial-setup). We can initialize a new project like so: ```bash theme={null} poetry new streamlit-app cd streamlit-app ``` > [!NOTE] > > If you're using Poetry 2.0+ (the default when installed via `pipx`), the `poetry shell` command is no longer built in. Use `poetry run` to run commands (e.g., `poetry run python main.py`), or activate the environment with `poetry env activate`. See the [initial setup guide](/python/the-basics/initial-setup) for more details. Then we have to add the streamlit dependency: ```bash theme={null} poetry add streamlit ``` Now, let's create a simple streamlit app in `main.py`: ```python theme={null} import streamlit as st st.write('hello from fly.io') ``` We can then serve the development version of the app using the `streamlit` cli tool: ```bash theme={null} streamlit run main.py ``` This will display a 'hello from fly.io!' message when you visit the root URL. Before deploying, we must add a bit of configuration to `.streamlit/config.toml`: ```toml theme={null} [server] headless = true ``` This ensures that our app doesn't give a prompt when we ship it. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a streamlit app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/[app-name] We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [multi-stage builds](/python/the-basics/multi-stage-builds). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/fly-apps/) for a reference. # Python on Fly.io Source: https://docs.fly.io/python/index Run Python apps on Fly.io close to users worldwide. Start with the FastAPI speedrun, then use The Basics to set up and manage production Python apps. Fly.io is a great place to launch your Python applications, especially if you plan on running them on multiple servers around the world so your users have a fast, snappy, low-latency experience. Still comparing hosts? See [Python hosting compared](https://fly.io/learn/python-hosting/). If you have questions or comments about running Python applications on Fly.io, create a new topic in our [community forum](https://community.fly.io/) and tag it with "python" so the right people can give you the support you need. ## Getting Started Run through the [FastAPI Speedrun](/python/frameworks/fastapi) to get a feel for what it's like to deploy on Fly.io. ## The Basics [The Basics](/python/the-basics) covers everything you need to set up, run, and manage production Python apps on Fly.io. These documents also identify some common problems you may encounter and provide some pointers on how to resolve them. # Python: The Basics Source: https://docs.fly.io/python/the-basics Python basics on Fly.io: install flyctl and run fly launch, set up a local Python environment with poetry, and shrink images with multi-stage builds. These guides will help you get through the basics of setting up your Python application with pieces of infrastructure commonly found in the wild. Most Python frameworks follow the same flow in terms of setup and vary only in their application code. For frameworks that also have a frontend component additional steps will be required. To develop Python applications, you will need to install python. We also recommend installing poetry to manage your dependencies. ## Install flyctl and log in In order to start working with Fly.io, you will need `flyctl`, our CLI app for managing apps. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). For most popular frameworks you can deploy your app with just one command: ```bash theme={null} fly launch ``` ## Setting up a Python Environment How to setup a functional python environment on your local machine. ## Multi-stage Builds A strategy to make your containers smaller in size and reduce attack surface. # Setting up a Python Environment Source: https://docs.fly.io/python/the-basics/initial-setup Set up a local Python environment with Poetry and virtual environments before deploying to Fly.io, with uv, pip, Pipenv and pyenv listed as alternatives. ## Initial Local Setup Make sure that [Python](https://www.python.org/) is already installed on your computer along with a way to create virtual environments. This allows you to run your project locally, and test that it works, before deploying it to Fly.io. > [!NOTE] > > We recommend the latest [supported versions](https://devguide.python.org/versions/#supported-versions) of Python. ## Dependency Management For project and dependency management we use [Poetry](https://python-poetry.org/). Like most package managers, Poetry combines multiple tools in one. You have other options: * [uv](https://docs.astral.sh/uv/) * [venv](https://docs.python.org/3/library/venv.html) and [pip](https://pip.pypa.io/) * [Pipenv](https://github.com/pypa/pipenv) * [pyenv](https://github.com/pyenv/pyenv) * [pip-tools](https://pypi.org/project/pip-tools/) If you are just starting out, it is easiest to follow along using `poetry`. After installing it, you will have to set 2 flags for virtual environment management. ```bash theme={null} poetry config virtualenvs.create true poetry config virtualenvs.in-project true ``` This will make your development environment resemble what ends up happening inside the Docker image. You can create a new project using this command: ```bash theme={null} poetry new ``` Once inside the project, you can add packages with the add command: ```bash theme={null} poetry add ``` This will automatically create a virtual environment for you. To interact with your virtual environment, you can prefix your commands with `poetry run`: ```bash theme={null} poetry run python main.py ``` Alternatively, you can activate the environment: ```bash theme={null} poetry env activate ``` This prints the activation command for your shell. To activate in one step: ```bash theme={null} source $(poetry env activate) python main.py ``` > [!NOTE] > > If you're using Poetry 2.0+ (the default when installed via `pipx`), the `poetry shell` command is no longer built in. Use `poetry run` (recommended) or `poetry env activate` as shown above. If you prefer `poetry shell`, you can install it as a plugin: `poetry self add poetry-plugin-shell`. # Multi-stage Builds Source: https://docs.fly.io/python/the-basics/multi-stage-builds Write a multi-stage Dockerfile for Python apps on Fly.io that builds dependencies with Poetry in a full image and copies them into a slim runtime image. Python apps need at the very least a Python runtime to be executed. However, sometimes you need much more to get a python app to work than just the runtime. Some dependencies need specific libraries and utilities to be built and compiled. For that reason many developers choose to use something called a multi-stage docker build. In short, we split the process of building and compiling dependencies and running the app. This is good for a number of reasons: * The resulting image is smaller in size * The 'attack surface' of your application is smaller Let's make a multi-stage `Dockerfile` from scratch. Here's part 1: > [!NOTE] > > In this example we assume the use of `poetry`, however you can adapt the file to work with other dependency managers too. ```dockerfile theme={null} FROM python:3.11.9-bookworm AS builder ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 RUN pip install poetry && poetry config virtualenvs.in-project true WORKDIR /app COPY pyproject.toml poetry.lock ./ RUN poetry install ``` So what's going on here? First, we use a "fat" python 3.11.9 image and installing and building all dependencies. Defining it as `builder` gives us a way to interact with it later. What essentially happens here is exactly what happens when you install a project locally using poetry: a `.venv/` directory is created and in it are all your built dependencies and binaries. You can inspect your own `.venv/` folder to see what that looks like. This directory is the primary artifact that we want. Part 2, the runtime, looks something like this: ```dockerfile theme={null} FROM python:3.11.9-slim-bookworm WORKDIR /app COPY --from=builder /app . COPY [python-app]/ ./[python-app] CMD ["/app/.venv/bin/python", "[python-app]/app.py"] ``` Here we see very little actually going on; instead of the "fat" image, we now pick the slim variant. This one is about 5 times smaller in size, but is unable to compile many of the dependencies we would want compiled. We have already done that part though, so we can copy that `.venv/` folder over to this image without having to compile it again. With this setup our image will be around 200MB most of the time (depending on what else you include). This setup is used for nearly all Python apps you deploy on Fly.io. > [!NOTE] > > The image size is largely dependent on what files you add in the dockerfile; by default the entire working directory is copied in. If you do not want to add certain files, you can specify them in a `.dockerignore` file. # Rails: Advanced Guides Source: https://docs.fly.io/rails/advanced-guides Advanced Rails on Fly.io: LiteFS, multiple Fly apps from one project, Phusion Passenger, multi-region deploys, AnyCable, the Machines API, and SQLite3. As your Rails application becomes more popular, it will inevitably grow in complexity, the number of people using it will increase, and pretty much everything else goes up and to the right. Fortunately Fly can reduce some of that complexity by making global deployments easy and provide some best practices for scaling a Rails application on Fly. ## LiteFS This guide shows use LiteFS with Rails ## Multiple Fly Applications At some point a Rails application will need to run additional services, like a Puppeteer server that takes screenshots of a webpage for your Rails application. Learn how to manage multiple applications from one Rails project to keep your monolith a monolith. ## Phusion Passenger This guide shows you how to replace the Puma web server with nginx and Phusion Passenger. ## Multi-region Deployments Architect Rails applications so they can run closer to your customers on servers deployed around the world. ## AnyCable This guide shows you how to replace Action Cable with AnyCable. ## Machine API This guide shows use the Fly Machine API using Rails ## SQLite3 This guide shows you how to use SQLite3 as your database # AnyCable Source: https://docs.fly.io/rails/advanced-guides/anycable Replace Action Cable with AnyCable in a Rails app on Fly.io, adding anycable-go, nginx and foreman to the Dockerfile and running everything on one 512MB VM. This guide shows you how to replace [Action Cable](https://guides.rubyonrails.org/action_cable_overview.html) with [AnyCable](https://docs.anycable.io/). Both provide similar functionality, but have different scaling characteristics. While AnyCable has the potential to reduce RAM requirements on large deployments, the need to run multiple processes makes it impossible to run AnyCable alongside even a tiny Rails application on a 256MB machine. A minimum of 512MB is required. The configuration below also runs [nginx](https://www.nginx.com/) as a [reverse proxy](https://www.nginx.com/resources/glossary/reverse-proxy-server/) to avoid any firewall issues. It also runs everything on one VM, which has a number of downsides including dropping socket connections every time a new version of your application is deployed. For another take on configuring AnyCable to run on fly.io, see [Fly.io Deployment](https://docs.anycable.io/deployment/fly) on the anycable site. ## Prepare your application If you haven't already done so, [create an Upstash Redis database](/upstash/redis). Also be sure that the `pg` gem is listed in your `Gemfile`. Now add the `anycable-rails` gem: ```bash theme={null} bundle add anycable-rails ``` Edit `config/cable.yml` thus: ```diff theme={null} production: - adapter: redis - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> - channel_prefix: namelist_production + adapter: any_cable ``` ## Update your image Modify `Dockerfile` to install `foreman`, `anycable-go` and `nginx`: ```diff theme={null} FROM base +RUN gem install foreman +RUN curl -L https://go.dev/dl/go1.19.linux-amd64.tar.gz | tar xz -C /opt +RUN GOBIN=/usr/local/bin /opt/go/bin/go install github.com/anycable/anycable-go/cmd/anycable-go@latest + -ARG PROD_PACKAGES="postgresql-client file vim curl gzip libsqlite3-0" +ARG PROD_PACKAGES="postgresql-client file vim curl gzip libsqlite3-0 nginx" ENV PROD_PACKAGES=${PROD_PACKAGES} RUN --mount=type=cache,id=prod-apt-cache,sharing=locked,target=/var/cache/apt \ --mount=type=cache,id=prod-apt-lib,sharing=locked,target=/var/lib/apt \ apt-get update -qq && \ apt-get install --no-install-recommends -y \ ${PROD_PACKAGES} \ && rm -rf /var/lib/apt/lists /var/cache/apt/archives + +ADD config/nginx.conf /etc/nginx/sites-available/default COPY --from=gems /app /app COPY --from=node_modules /app/node_modules /app/node_modules ``` Edit `fly.toml` to run foreman:: ```diff theme={null} [env] PORT = "8080" - SERVER_COMMAND = "bin/rails fly:server" + SERVER_COMMAND = "foreman start -f Procfile.fly" ``` Add `Procfile.fly` to start the four processes required: ``` nginx: nginx -g 'daemon off;' server: bin/rails server -p 8081 anycable: bundle exec anycable ws: anycable-go --port=8082 ``` Add `config/nginx.conf` to reverse proxy cable traffic to anycable-go and send the remainder to your Rails application:: ``` server { listen 8080 default_server; listen [::]:8080 default_server; location /cable { proxy_pass http://localhost:8082/cable; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; } location / { proxy_pass http://localhost:8081/; proxy_set_header origin 'https://localhost:8081'; } } ``` ## Deployment If you haven't already done so, scale your machine: ```bash theme={null} fly scale vm shared-cpu-1x --vm-memory 512 ``` Now you are ready to deploy: ``` fly deploy ``` # LiteFS Source: https://docs.fly.io/rails/advanced-guides/litefs Replicate a Rails app's SQLite database across regions with LiteFS on Fly.io, using the dockerfile-rails generator and fly machine clone to add a replica. > [!NOTE] > > **Important:** LiteFS is outside our [scope of support](/about/support#scope-of-support). We are not able to provide support or guidance for it. Use with caution. > [!WARNING] > > **Don't use LiteFS with autostop/autostart on Fly Machines.** > > The Fly Proxy can stop and restart Machines without knowing which Machine holds the LiteFS lease or has the latest data. This can allow a Machine with stale data to acquire the lease, causing LiteFS to discard newer changes and LTX files. The result can be a database rollback and permanent data loss. This is a technology preview. It shows how to do multi-region deployments using Sqlite3 and Litefs. See [LiteFS - Distributed SQLite](/litefs) for background. In order to run this demo, you need `flyctl` to be version `0.1.9` or later. ## Deploying a Rails project as a Fly.io Machine Let's start with a very simple Rails project: ```sh theme={null} rails new list cd list bin/rails generate scaffold Name name echo 'Rails.application.routes.draw {root "names#index"}' >> config/routes.rb ``` Launching this will require a litefs configuration file (`litefs.yml`) and a number of changes to your dockerfile. Fly.io provides a [dockerfile generator](https://github.com/fly-apps/dockerfile-rails) which will do this for you. Run it immediately after `fly launch` thus: ```sh theme={null} fly launch bin/rails generate dockerfile --litefs ``` `fly launch` will prompt you for a name, region, and whether or not you want postgres or redis databases. Say no to the databases, you won't need them for this demo. `generate dockerfile` will prompt you whether or not you want to accept the changes. Feel free to peruse the diffs, but ultimately accept the changes. If you would rather not even be prompted to see the diffs, you can add a `--force` option the command. Before we deploy, let's make a one-line change to our `fly.toml` to keep our machines running so that we can ssh into them whenever we want: ```toml theme={null} auto_stop_machines = false ``` Now we can deploy normally: ``` fly deploy ``` Once the application has been deployed, running `fly apps open` will open a browser. Add one name. Return back to your terminal window and run: ```bash theme={null} fly machines list -q ``` You will see that only one copy of your application is running. You can deploy a second machine in a different region using: ```bash theme={null} fly machine clone --region lhr 3d8d9930b32189 ``` Feel free to pick a different region. Substitute the machine id with the one that you see in the response to `fly machines list -q`. Once both instances are running, enter the following command and select the instance that is furthest from you: ``` % fly ssh console -s ? Select VM: [Use arrows to move, type to filter] atl: 3d8d9930b32189 fdaa:0:d445:a7b:e5:b340:6b3d:2 autumn-breeze-5346 > lhr: e784e90ea17928 fdaa:0:d445:a7b:13e:8621:f8bd:2 muddy-moon-3291 ``` Once you see a prompt, verify that you landed where you expected: ```bash theme={null} printenv FLY_REGION ``` Now run rails console and display the last name: ``` % /rails/bin/rails console Loading production environment (Rails 7.0.4) irb(main):001:0> Name.last ``` Return to the browser and change the value of this name, and then once again use the rails console to verify that the name has been updated. #### Current limitations * [What Litefs Can Do Today](https://fly.io/blog/introducing-litefs/#what-litefs-can-do-today) describes current capabilities and future plans. # Machine API Source: https://docs.fly.io/rails/advanced-guides/machine Launch Fly Machines from a Rails Active Job using the fly.io-rails gem, running each background task on its own Machine and deleting it when it exits on Fly.io. This is a technology preview. It demonstrates how you can launch fly machines dynamically to perform background tasks from within a Rails application. ## Deploying a Rails project as a Fly.io Machine ```bash theme={null} rails new welcome; cd welcome ``` Now use your favorite editor to make a one line change to `config/routes.rb`: ```diff theme={null} Rails.application.routes.draw do # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") - # root "articles#index" + root "rails/welcome#index" end ``` Install `fly.io-rails` gem: ```bash theme={null} bundle add fly.io-rails ``` Source to this gem is on [GitHub](https://github.com/rubys/fly.io-rails). If adopted, it will move to the superfly organization. Optionally configure your project: ```bash theme={null} bin/rails generate fly:app --passenger --serverless ``` The above command will configure your application to scale to zero whenever it has been idle for 5 minutes. See [generator options](https://github.com/rubys/fly.io-rails#generator-options) for more details. Feel free to tailor the generated files further to suit your needs. If you don't run the `fly:app` generator, the files necessary to deploy your application will be generated on your first deploy using default options. Deploy your project ```bash theme={null} bin/rails deploy ``` You have now successfully deployed a trivial Rails app Fly.io machines platform. You can verify that this is running on the machines platform via `fly status`. You can also run commands like `fly apps open` to bring your application up in the browser. Now lets make that application launch more machines. ## Installing `fly` on the Rails Machine Since we will be using fly services from within our Rails application, we will need to install the fly executable. We do that by adding the following lines to our `Dockerfile`: ``` RUN curl -L https://fly.io/install.sh | sh ENV FLYCTL_INSTALL="/root/.fly" ENV PATH="$FLYCTL_INSTALL/bin:$PATH" ``` A good place to put these lines is immediately before the `# Deploy your application` comment. Next we need to make a Fly token available to our application: ```bash theme={null} fly secrets set FLY_API_TOKEN=$(fly tokens deploy) ``` ## Add a controller Lets generate a controller with three actions: ```bash theme={null} bin/rails generate controller job start complete status ``` The three actions will be as follows: * `job/start` will be the URL you will load that will kick off a job. * `job/complete` will be the URL that job will fetch once it is complete. * `job/status` will be the URL you will load once the job is complete to see the results. To keep things simple, all we are going to do is have these tasks write timestamps to a file, one when the job starts, and one when the job completes. Status will return the results of the file. The code to do this is straightforward: ```ruby theme={null} class JobController < ApplicationController skip_before_action :verify_authenticity_token def start File.write 'tmp/status', `date +"%d-%m-%Y %T.%N %Z"` url = "http://#{request.host_with_port}/job/complete" job = MachineJob.perform_later(url) render plain: "#{job}\n", layout: false end def complete File.write 'tmp/status', `date +"%d-%m-%Y %T.%N %Z"`, mode: 'a+' render plain: "OK\n", layout: false end def status render plain: IO.read('tmp/status'), layout: false end end ``` Note that the `start` method provides the complete URL of the `complete` action as a parameter to the machine job. Before moving on, lets make sure that the file exists: ```bash theme={null} touch tmp/status ``` ## Add a Job We start by generating a job: ```bash theme={null} bin/rails generate job machine ``` Overall the tasks to be performed by this job: * Specify a machine configuration. For simplicity we will use the same Fly application name and the same Fly image as our Rails application. The server command will be `curl` specifying the URL that was passed as an argument to the job. * Start a machine using this configuration, and check for errors, and log the results. * Query the status of the machine every 10 seconds for a maximum of 5 minutes, checking to see if the machine has exited. * Extract the exit code and log the state. If the machine has exited successfully we delete the machine, otherwise we leave it around so that further forensics can be performed. The implementation of this plan is as follows: ```ruby theme={null} require 'fly.io-rails/machines' class MachineJob < ApplicationJob queue_as :default def perform(url) if Rails.env.production? # specify a machine configuration app = ENV['FLY_APP_NAME'] config = { image: ENV['FLY_IMAGE_REF'], guest: {cpus: 1, memory_mb: 256, cpu_kind: "shared"}, env: {'SERVER_COMMAND' => "curl #{url}"} } # start a machine start = Fly::Machines.create_and_start_machine(app, config: config) machine = start[:id] if machine logger.info "Started machine: #{machine}" else logger.error 'Error starting job machine' logger.error JSON.pretty_generate(start) return end # wait for machine to complete, checking every 10 seconds, # and timing out after 5 minutes. event = nil 30.times do sleep 10 status = Fly::Machines.get_a_machine app, machine event = status[:events]&.first break if event && event[:type] == 'exit' end # extract exit code exit_code = event.dig(:request, :exit_event, :exit_code) if exit_code == 0 # delete job machine delete = Fly::Machines.delete_machine app, machine if delete[:error] logger.error "Error deleting machine: #{machine}" logger.error JSON.pretty_generate(delete) else logger.info "Deleted machine: #{machine}" end else logger.error 'Error performing job' logger.error (exit_code ? {exit_code: exit_code} : event).inspect end else system "curl", url end end end ``` Note in particular the calls to `Fly::Machines`: * [`Fly::Machines.create_and_start_machine`](/machines/api/machines-resource#create-a-machine) * [`Fly::Machines.get_a_machine`](/machines/api/machines-resource#get-a-machine) * [`Fly::Machines.delete_machine`](/machines/api/machines-resource#delete-a-machine-permanently) Each of the lines in the list above is a link to the documentation for that API. The key difference is that instead of passing in and receiving back a JSON object, you pass in and receive back a Ruby hash. And all of the URLs and HTTP headers are taken care of for you by the `Fly::Machine` module. For those interested in the inner workings, the source to [Fly::Machine](https://github.com/rubys/fly.io-rails/blob/main/lib/fly.io-rails/machines.rb) is on GitHub. Again, all this is beta, and subject to change. ## Trying it out We are now ready to deploy, but before we do in a separate window start watching the log:: ```bash theme={null} fly logs ``` Now deploy the application: ```bash theme={null} bin/rails deploy ``` If you run `fly apps open` you will arrive at your application's welcome page. Take a note of the URL. Either in the browser or in a command window add `/job/start`. As a response (either in your browser or terminal window you will see something like: ``` # ``` Switching to your log window you should see output similar to the following: ``` 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]I, [2022-09-20T04:07:59.525790 #514] INFO -- : [b2f7eb1f-e552-445d-84ac-72d4a63fa4d4] Started GET "/job/start" for 168.220.92.2 at 2022-09-20 04:07:59 +0000 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]I, [2022-09-20T04:07:59.529213 #514] INFO -- : [b2f7eb1f-e552-445d-84ac-72d4a63fa4d4] Processing by JobController#start as HTML 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]I, [2022-09-20T04:07:59.541820 #514] INFO -- : [b2f7eb1f-e552-445d-84ac-72d4a63fa4d4] [ActiveJob] Enqueued MachineJob (Job ID: 7984003d-bb82-4815-acff-81d1ba91539f) to Async(default) with arguments: "http://weathered-sunset-3812.fly.dev/job/complete" 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]I, [2022-09-20T04:07:59.544837 #514] INFO -- : [b2f7eb1f-e552-445d-84ac-72d4a63fa4d4] Rendered text template (Duration: 0.0ms | Allocations: 8) 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]I, [2022-09-20T04:07:59.545257 #514] INFO -- : [b2f7eb1f-e552-445d-84ac-72d4a63fa4d4] Completed 200 OK in 16ms (Views: 2.9ms | Allocations: 1056) 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]I, [2022-09-20T04:07:59.546683 #514] INFO -- : [ActiveJob] [MachineJob] [7984003d-bb82-4815-acff-81d1ba91539f] Performing MachineJob (Job ID: 7984003d-bb82-4815-acff-81d1ba91539f) from Async(default) enqueued at 2022-09-20T04:07:59Z with arguments: "http://weathered-sunset-3812.fly.dev/job/complete" 2022-09-20T04:07:59Z app[9080514c12d787] iad [info]E, [2022-09-20T04:07:59.546905 #514] ERROR -- : [ActiveJob] [MachineJob] [7984003d-bb82-4815-acff-81d1ba91539f] danger 2022-09-20T04:07:59Z runner[5683009c17548e] iad [info]Reserved resources for machine '5683009c17548e' 2022-09-20T04:07:59Z runner[5683009c17548e] iad [info]Pulling container image 2022-09-20T04:08:00Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:00.071564 #514] INFO -- : [ActiveJob] [MachineJob] [7984003d-bb82-4815-acff-81d1ba91539f] Started machine: 5683009c17548e 2022-09-20T04:08:00Z runner[5683009c17548e] iad [info]Unpacking image 2022-09-20T04:08:02Z runner[5683009c17548e] iad [info]Configuring firecracker 2022-09-20T04:08:03Z app[5683009c17548e] iad [info] % Total % Received % Xferd Average Speed Time Time Time Current 0 0 0 0 0 0 load Upload Total Spent Left Speed 2022-09-20T04:08:04Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:04.813284 #514] INFO -- : [c6131d3b-bcba-4ab7-88cb-3e07800ec6b2] Started GET "/job/complete" for 2a09:8280:1::7635 at 2022-09-20 04:08:04 +0000 2022-09-20T04:08:04Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:04.814303 #514] INFO -- : [c6131d3b-bcba-4ab7-88cb-3e07800ec6b2] Processing by JobController#complete as */* 2022-09-20T04:08:04Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:04.826253 #514] INFO -- : [c6131d3b-bcba-4ab7-88cb-3e07800ec6b2] Rendered text template (Duration: 0.0ms | Allocations: 2) 2022-09-20T04:08:04Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:04.827434 #514] INFO -- : [c6131d3b-bcba-4ab7-88cb-3e07800ec6b2] Completed 200 OK in 13ms (Views: 2.1ms | Allocations: 167) 100 3 0 3 0 0 1 0 --:--:-- 0:00:02 --:--:-- 2 2022-09-20T04:08:04Z app[5683009c17548e] iad [info]OK 2022-09-20T04:08:07Z runner[5683009c17548e] iad [info]machine exited with exit code 0, not restarting 2022-09-20T04:08:10Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:10.238155 #514] INFO -- : [ActiveJob] [MachineJob] [7984003d-bb82-4815-acff-81d1ba91539f] Deleted machine: 5683009c17548e 2022-09-20T04:08:10Z app[9080514c12d787] iad [info]I, [2022-09-20T04:08:10.238536 #514] INFO -- : [ActiveJob] [MachineJob] [7984003d-bb82-4815-acff-81d1ba91539f] Performed MachineJob (Job ID: 7984003d-bb82-4815-acff-81d1ba91539f) from Async(default) in 10691.24ms ``` And, finally, visit `/job/status` to see the results. Durations will vary, but I'm currently seeing a total elapsed time of anywhere from about two and a half seconds to five seconds. ## Recap While not exactly a realistic application, this application demonstrates a number of important features. Scheduling a job. Launching, monitoring, and removing a machine. Inter-machine communications. With the right parameters, you can start machines in remote geographic locations or with volumes attached. These machines will have access to your application's secrets so they can access databases or other cloud services. And if you go back and look at the `main.tf` file in your application directory you can get an indication of what steps are required, and what options are required for each step, to launch a Rails application. The possibilities are unlimited. At the moment, this is only a preview, so API names and options may change. But do try this out, and if you have questions or come up with an exciting usage of this, let us know on [community.fly.io](https://community.fly.io/). # Multi-region Deployments Source: https://docs.fly.io/rails/advanced-guides/multi-region Run a Rails app in multiple regions on Fly.io, with pointers to the fly-ruby gem, global Redis for caching and Action Cable, and Postgres read replicas. Rails applications that are successful often end up with users from all parts of the world. Learn how to make them fast and responsive for everybody on the planet. ## The `fly-ruby` gem The [fly-ruby gem](https://github.com/superfly/fly-ruby) provides a basic toolkit for making Rails applications across multiple regions. Read [Run Ordinary Rails Apps Globally](https://fly.io/blog/run-ordinary-rails-apps-globally/) to see how the gem can be used in a Rails application to make it work across multiple regions. ## Redis Since many Rails applications depend on Redis for caching, background workers, and Action Cable, it's important to think through deploying Redis globally. * [https://fly.io/blog/last-mile-redis/](https://fly.io/blog/last-mile-redis/) ## Postgres Postgres also requires consideration for global deployments. * [High Availability & Global Replication](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication) # Rails: Multiple Fly Applications Source: https://docs.fly.io/rails/advanced-guides/multiple-applications Run extra services like Redis alongside a Rails project as separate Fly.io apps, each in its own subfolder with its own fly.toml, deployed on its own. This guide discusses how to manage multiple Fly applications within a Rails projects. This is useful for Rails projects that need to run other services, like running a pool of Puppeteer servers that your Rails app calls to take screenshots of web pages. ## What is a Fly application? Your Rails application is a Fly application, which means it has the following few things: * A `fly.toml` file in the root directory * A `Dockerfile` in the root directory that describes the image * A "server" running on Fly's infrastructure. So how do you spin up multiple Fly applications for a single Rails project? ## Creating a Fly application within a Fly application The important thing about creating multiple Fly applications within a project is keeping them organized. For our example, we'll setup a Redis server application and keep it in the `./fly/applications` folder within our project repo. Let's get started by running the following commands: ```bash theme={null} mkdir -p fly/applications/redis cd fly/applications/redis ``` From inside the `fly/applications/redis` folder, run: ```bash theme={null} fly launch --image flyio/redis:6.2.6 --no-deploy --name my-project-name-redis ``` This command will create a `Dockerfile` and `fly.toml` file that can be further configured for your application's needs. Next, deploy the application: ```bash theme={null} fly deploy ``` ## Accessing from the root application Fly creates DNS hosts for each of your applications that are not surprising. ## Deploying updates In the future, when it's time to deploy updates to your Fly application within a Fly application, run: ```bash theme={null} cd fly/application/redis fly deploy ``` That's it. # Phusion Passenger Source: https://docs.fly.io/rails/advanced-guides/phusion-passenger Replace Puma with nginx and Phusion Passenger in a Rails app on Fly.io using the passenger-full Docker image, plus nginx site and environment variable config. This guide shows you how to replace the Puma web server which runs your Rails application with [nginx](https://www.nginx.com/) and [Phusion Passenger](https://www.phusionpassenger.com/). This may be useful if you need functionality that nginx and/or Passenger provide, such as reverse proxying or hosting multiple applications. ## Replacing the Dockerfile When you previously ran `fly launch` you were provided with a Dockerfile that was used to package your application. This Dockerfile provided reasonable defaults. One of those defaults was to choose the Puma web server. This is the same default that Rails provides for new applications. This Dockerfile can be customized or replaced to meet your needs. This guide will show you how to replace the Dockerfile with one that chooses Phusion Passenger. The new Dockerfile will make use of the [phusion/passenger-full](https://github.com/phusion/passenger-docker#docker-base-images-for-ruby-python-nodejs-and-meteor-web-apps) image. To get started, replace your Dockerfile with the following: ``` FROM phusion/passenger-full:2.3.0 RUN rm -f /etc/service/nginx/down RUN rm -f /etc/service/redis/down RUN rm /etc/nginx/sites-enabled/default ADD config/fly/rails.conf /etc/nginx/sites-enabled/rails.conf ADD config/fly/envvars.conf /etc/nginx/main.d/envvars.conf ENV RAILS_LOG_TO_STDOUT true ARG BUNDLE_WITHOUT=development:test ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT} RUN mkdir /app WORKDIR /app RUN mkdir -p tmp/pids COPY Gemfile* ./ RUN bundle install ENV SECRET_KEY_BASE 1 COPY . . RUN bundle exec rails assets:precompile CMD ["/sbin/my_init"] ``` As promised, the passenger-full image that the Phusion team provides makes your Dockerfile considerably smaller, but there remains more work to be done as you still need to configure nginx. We do that next. ## Configuring nginx The Dockerfile above contains three `ADD` commands. These copy configuration files to the image. The first two files configure nginx. Place all three files in a `config/fly` directory. We start with `config/fly/rails.conf`: ``` server { listen 8080; server_name www.webapp.com; root /app/public; # The following deploys your Ruby/Python/Node.js/Meteor app on Passenger. # Not familiar with Passenger, and used (G)Unicorn/Thin/Puma/pure Node before? # Yes, this is all you need to deploy on Passenger! All the reverse proxying, # socket setup, process management, etc are all taken care automatically for # you! Learn more at https://www.phusionpassenger.com/. passenger_enabled on; passenger_user app; # If this is a Ruby app, specify a Ruby version: # For Ruby 3.1 passenger_ruby /usr/bin/ruby3.1; # For Ruby 3.0 # passenger_ruby /usr/bin/ruby3.0; # For Ruby 2.7 # passenger_ruby /usr/bin/ruby2.7; # For Ruby 2.6 # passenger_ruby /usr/bin/ruby2.6; # Nginx has a default limit of 1 MB for request bodies, which also applies # to file uploads. The following line enables uploads of up to 50 MB: client_max_body_size 50M; } ``` The servername doesn't particularly matter, but you can set it to the name of the machine your application will be deployed to. The only real customization required is to select the version of Ruby desired. Next we need to identify what environment variables will be used by the application. See the [passenger documentation](https://github.com/phusion/passenger-docker#setting-environment-variables-in-nginx) for more details. We do that by placing the following into `config/fly/envvars.conf`: ``` env DATABASE_URL; env REDIS_URL; env RAILS_LOG_TO_STDOUT; ``` The above are commonly used variables, feel free to adjust as you see fit. ## Enabling swap See [`swap_size_mb`](/reference/configuration#swap_size_mb-option) for configuring for deploys. ## Deployment That's it. As always you deploy your application via `fly deploy` and can open it via `fly apps open`. Everything else remains the same. You can use your same Postgre database, redis data store, and any other secrets you may have set. Both Puma and Passenger are excellent choices for application servers for your Rails application, so you normally wouldn't have a need to replace one with the other. Further configuration likely is required to unlock specific features of nginx or passenger for your particular needs. The above is only intended as an initial configuration to get you started. You have full control over what is installed on your machine and how both nginx and passenger are configured. The sky's the limit on what you can achieve with these instructions! # Rails: SQLite3 Source: https://docs.fly.io/rails/advanced-guides/sqlite3 Use SQLite3 as the database for a Rails app on Fly.io by creating a volume, mounting it in fly.toml, and pointing DATABASE_URL at a file on the volume. 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](/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 sqlite3 for their database can't be deployed to multiple regions. But if you are okay using beta software, [LiteFS](/litefs) could work for multi-region sync, check it out! But this guide assumes you have one node and one volume. Following are the steps required to make this work: ## Create volume ```bash theme={null} fly volumes create name ``` Replace `name` with your desired volume name. Only alphanumeric characters and underscores are allowed in names. Optionally, you may specify the size of the volume, in gigabytes, by adding a `--size int` argument. The default volume size is 3 gigabytes. Now set the following secret, again replacing the name with what you selected: ```bash theme={null} fly secrets set DATABASE_URL=sqlite3:///mnt/name/production.sqlite ``` ## Mount and Prep for Deployment Add the following to your `fly.toml`, once again replacing the name with what you selected, this time in two places: ``` [mounts] source="name" destination="/mnt/name" ``` Next move the dependency on the `db:migrate` task from the `release` to `server` in `lib/tasks/fly.rake`: ```diff theme={null} # commands used to deploy a Rails application namespace :fly do # BUILD step: # - changes to the filesystem made here DO get deployed # - NO access to secrets, volumes, databases # - Failures here prevent deployment task :build => 'assets:precompile' # RELEASE step: # - changes to the filesystem made here are DISCARDED # - full access to secrets, databases # - failures here prevent deployment - task :release => 'db:migrate' + task :release # SERVER step: # - changes to the filesystem made here are deployed # - full access to secrets, databases # - failures here result in VM being stated, shutdown, and rolled back # to last successful deploy (if any). - task :server do + task :server => 'db:migrate' do sh 'bin/rails server' end end ``` You can also silence warnings about running sqlite3 in production by adding the following line to config/environments/production.rb: ```diff theme={null} Rails.application.configure do + config.active_record.sqlite3_production_warning=false ``` ## Deploy These changes can be deployed using `fly deploy`. # Rails API-only Applications Source: https://docs.fly.io/rails/cookbooks/api Deploy a Rails API-only app with a React client on Fly.io: a multi-stage Dockerfile builds the client into public/ and a controller serves JSON versions. Rails [API-only](https://guides.rubyonrails.org/api_app.html) Applications are composed of two parts: a streamlined Rails server typically serving JSON responses, and a JavaScript client, typically using a framework such as React, Vue, or Angular. ## Minimal Dockerfile Below is a Dockerfile that deploys a [Create React App](https://create-react-app.dev/) client with a Rails API server: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM node:slim AS react RUN npx create-react-app client RUN cd client; npm run build FROM ruby:slim AS build RUN apt-get update &&\ apt-get install --yes build-essential git RUN gem install rails RUN rails new demo --minimal --skip-active-record --api FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=react /client/build /demo/public WORKDIR demo ENV RAILS_ENV=production ENV RAILS_SERVE_STATIC_FILES=true EXPOSE 3000 CMD bin/rails server ``` Differences from a minimal Rails app: * A `react` build stage based on the `node:slim` image and consisting of two `RUN` steps: one that creates a react app, and one that builds it. * An `--api` flag is added to the `rails new` command. * A `COPY` statement copies the build artifacts to the demo application's public directory. * `RAILS_SERVE_STATIC_FILES` is set to true. This is necessary as [`[[statics]]`](/reference/configuration#the-statics-sections) won't find index.html files at the root. Even so, adding a statics section to the `toml` file is useful for the remainder of the bundled assets: ``` [[statics]] guest_path = "/demo/public" url_prefix = "/" ``` If the above is deployed you will see a spinning react logo. ## Example Following is a example that demonstrates a React client working with a Rails API server. First the React client: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM node:slim AS react RUN npx create-react-app client RUN node --version > client/.node-version COPY <<-"EOF" client/src/App.js import logo from './logo.svg'; import './App.css'; import React, { useState, useEffect } from 'react'; function App() { let [versions, setVersions] = useState('loading...'); useEffect(() => { fetch('api/versions') .then(response => response.json()) .then(versions => { setVersions(Object.entries(versions) .map(([name, version]) => `${name}: ${version}`).join(', ') ) }); }); return (
logo

{ versions }

); } export default App; EOF RUN cd client; npm run build FROM ruby:slim AS build RUN apt-get update &&\ apt-get install --yes build-essential git RUN gem install rails RUN rails new demo --minimal --skip-active-record --api FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=react /client/build /demo/public COPY --from=react /client/.node-version /demo WORKDIR demo RUN bin/rails generate controller Api versions COPY <<-"EOF" app/controllers/api_controller.rb class ApiController < ApplicationController def versions render json: { ruby: RUBY_VERSION, rails: Rails::VERSION::STRING, node: IO.read('.node-version').strip.sub(/^v/, '') } end end EOF ENV RAILS_ENV=production ENV RAILS_SERVE_STATIC_FILES=true EXPOSE 3000 CMD bin/rails server ``` The above captures the node version into a file, and defines a React hook that fetches version information from the API server and renders the rotating React logo followed by this version information. Now the server implementation: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM node:slim AS react RUN npx create-react-app client RUN node --version > client/.node-version COPY <<-"EOF" client/src/App.js import logo from './logo.svg'; import './App.css'; import React, { useState, useEffect } from 'react'; function App() { let [versions, setVersions] = useState('loading...'); useEffect(() => { fetch('api/versions') .then(response => response.json()) .then(versions => { setVersions(Object.entries(versions) .map(([name, version]) => `${name}: ${version}`).join(', ') ) }); }); return (
logo

{ versions }

); } export default App; EOF RUN cd client; npm run build FROM ruby:slim AS build RUN apt-get update &&\ apt-get install --yes build-essential git RUN gem install rails RUN rails new demo --minimal --skip-active-record --api FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=react /client/build /demo/public COPY --from=react /client/.node-version /demo WORKDIR demo RUN bin/rails generate controller Api versions COPY <<-"EOF" app/controllers/api_controller.rb class ApiController < ApplicationController def versions render json: { ruby: RUBY_VERSION, rails: Rails::VERSION::STRING, node: IO.read('.node-version').strip.sub(/^v/, '') } end end EOF ENV RAILS_ENV=production ENV RAILS_SERVE_STATIC_FILES=true EXPOSE 3000 CMD bin/rails server ``` This consists of an additional `COPY` statement to add the `.node-version` information to the image, and a controller that returns various version strings as a JSON object. ## Recap From a Fly.io perspective what you have is a standard Rails application with an additional build step. That build step bundles the client into static assets (HTML, CSS, and JavaScript). # Optimizing your build Source: https://docs.fly.io/rails/cookbooks/build Speed up Rails Docker builds on Fly.io: copy Gemfile before source, run bundle and yarn installs in parallel stages, and reuse gems with a RUN cache mount. ## Ordering steps The initial cookbooks in this series seemed to automatically cache build steps and perform predictably - make a change to the Dockerfile and the build will pick up from the point of the change. In such cases, when there are steps that can be executed in any order it is a good idea to order the steps that take longest and/or are least likely to change earlier than quick steps that may change more frequently. This works well until you get to the point where steps need to be ordered. Now that you are copying sources from outside of the Dockerfile, a change to *any* source will trigger a rebuild from the point of the COPY statement on, including time consuming steps like `bundle install` and `yarn install`. The relevant portion of the Dockerfile looks something like the following: ``` COPY . . RUN bundle install ``` Splitting that into two copy statements can make a dramatic improvement in build times: ``` COPY Gemfile* . RUN bundle install COPY . . ``` As the first statement will only copy `Gemfile` and `Gemfile.lock`, the time consuming `bundle install` step will *only* be run if these two specific files have changed. This can result in a dramatic reduction in build times for cases where the Gemfile did not change. ## Multi-stage builds Now consider `bundle install` and `yarn install`. Both can be time consuming. They can be run in either order. If run sequentially, you will be faced with a choice: should a change to the `Gemfile` result in an unnecessary reinstall of node modules, or should a change to `package.json` result in an unnecessary reinstall of gems? We've seen how multi-stage builds can reduce image size. They also can be used to reduce build times. An example to illustrate: ``` FROM ruby:slim as base RUN apt-get install -y build-essential && volta install node@lts yarn@latest WORKDIR /demo FROM base as gems COPY Gemfile* . RUN bundle install FROM base as node COPY package*.json . RUN yarn install FROM base RUN apt-get install -y postgresql-client COPY . . COPY --from=gems /usr/local/bundle /usr/local/bundle COPY --from=node /demo/node-modules /demo/node-modules ``` Such a Dockerfile will only run `bundle install` if the `Gemfile` has changed, and only run `yarn install` if `package.json` changed. Even better, if both changed, they will be run concurrently. In fact, on the first run they will be run concurrently with the installation of `postgresql-client`. ## Caching We've seen how splitting COPY statements can reduce the number of steps, but it still remains the case that any change to the Gemfile will result in reinstalling all gems. This can be improved by using the [dedicated RUN cache](https://docs.docker.com/build/cache/#id=%22use-the-dedicated-run-cache%22). Applied to bundle installs, the resulting build instructions would look something like the following: ``` RUN --mount=type=cache,id=dev-gem-cache,sharing=locked,target=/srv/vendor \ bundle config set app_config .bundle && \ bundle config set without 'development test' && \ bundle config set path /srv/vendor && \ bundle install && \ bundle clean && \ mkdir -p vendor && \ bundle config set path vendor && \ cp -ar /srv/vendor . ``` That's a lot to unpack. Statement by statement: * a `gem-cache` directory is mounted on `/srv/vendor` * the bundle config directory is set to be a `.bundle` subdirectory of the current application. * gems marked as *development* or *test* in the Gemfile are not to be installed. * the bundle directory is set to the `/srv/vendor` directory * the install is performed * unused gems are removed * a vendor subdirectory is created. * the bundle directory is changed to be `vendor` subdirectory * the contents of the `/srv/vendor` cache is copied to the vendor subdirectory. The final build stage can copy the entire app directory from the build stage that included the above `RUN` statement to pick up the configuration as well as the gems. With this in place, adding a single gem to your Gemfile will result in only the installation of that one gem. ### Recap Starting from a simple sequence of two statements (potentially three if `yarn install` is added) we have explored a number of techniques involving ordering, splitting, staging, and caching of statements and results. We started with something that was simple and slow and ended with a solution that is considerably less simple but decidedly faster. This results in a trade-off. For small projects it may make sense to only adopt some of these techniques to keep the Dockerfile maintainable. For other projects it may make sense to incorporate more. # Rails with Databases Source: https://docs.fly.io/rails/cookbooks/databases Add databases to a Rails Dockerfile on Fly.io: Upstash Redis for Action Cable, SQLite on a volume, then Postgres via fly postgres attach, or MySQL. ## Initial Dockerfile Below is a minimal, multi-stage build, Dockerfiler that is capable of supporting both ActiveRecord and Action Cable: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby:slim as build RUN apt-get update &&\ apt-get install --yes build-essential git pkg-config redis RUN gem install rails RUN rails new demo --css tailwind FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle WORKDIR demo COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile RUN bin/rails db:prepare EXPOSE 3000 ENV RAILS_LOG_TO_STDOUT=true ENV RAILS_SERVE_STATIC_FILES=true CMD bin/rails server ``` This Dockerfile contains a number of small changes from the minimal one: * For Active Record (or more precisely, sqlite3) to work, we need to add `pkg-config` to the list of packages installed via `apt-get`. * For Action Cable to work, we need to add `redis` to the list of packages installed via `apt-get`. * `--minimal` and `--skip-active-record` are removed from the `rails new` line. * Since the example below uses Tailwindcss, we need to add `--css tailwind` to the `rails new` line. * We need to add `RUN bin/rails assets:precompile` and `RUN bin/rails db:prepare` immediately after the `ENV RAILS_ENV=production` line. * In order to see the log messages, add `ENV RAILS_LOG_TO_STDOUT=true` immediately before the `CMD bin/rails server` line. * We need to serve the assets. Chose one of the following two methods: * Set `ENV RAILS_SERVE_STATIC_FILES=true` * Add to the bottom of `fly.toml`: ``` [[statics]] guest_path = "/demo/public" url_prefix = "/" ``` In general, requests that never make it to Rails are served fastest, so the second approach where fly serves the static files is the better approach. *** ## Example application In order to demonstrate Action Cable and Active Record functionality, we need a Rails application. Following is a simple visitors counter. Replace the three lines starting with `COPY <<-"EOF" config/routes.rb` with the following: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby:slim as build RUN apt-get update &&\ apt-get install --yes build-essential git pkg-config redis RUN gem install rails RUN rails new demo --css tailwind FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle WORKDIR demo RUN bin/rails generate model Visitor counter:integer &&\ bin/rails generate channel counter COPY <<-"EOF" app/controllers/visitors_controller.rb class VisitorsController < ApplicationController def counter @visitor = Visitor.find_or_create_by(id: 1) @visitor.update! counter: @visitor.counter.to_i + 1 @visitor.broadcast_replace_later_to 'counter', partial: 'visitors/counter' end end EOF COPY <<-"EOF" app/views/visitors/counter.html.erb <%= turbo_stream_from 'counter' %>
The monochrome white Fly.io brandmark on a navy background
<%= render "counter", visitor: @visitor %>
EOF COPY <<-"EOF" app/views/visitors/_counter.html.erb <%= turbo_frame_tag(dom_id visitor) do %> <%= visitor.counter.to_i %> <% end %> EOF COPY <<-"EOF" config/routes.rb Rails.application.routes.draw { root "visitors#counter" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile RUN bin/rails db:prepare EXPOSE 3000 ENV RAILS_LOG_TO_STDOUT=true ENV RAILS_SERVE_STATIC_FILES=true CMD bin/rails server ``` Since this isn't a Rails tutorial, an overview of the contents will suffice: * A `Vistor` model with a single counter column. * A `Counter` channel to send realtime updates. * A controller method that updates the model, broadcasts the results, and renders the initial page. * HTML that connects to the channel, displays the fly.io logo, and renders a partial. * A partial that shows the counter inside a turbo frame. * A route that connects the root to the controller method we defined above. ## Setting up Redis Before we deploy this application, we need to create the redis database. We will be using [Upstash for Redis](/upstash/redis), and creating the database and connecting it to the demo application is a matter of issuing two commands: ``` flyctl redis create flyctl secrets set \ REDIS_URL=redis://default:REDACTED@NAME-redis.upstash.io ``` Replace the secret with the private URL provided by the `redis create` command. And you don't need to split the `secrets set` line, that was merely done to fit in your browser window. If you have deployed the minimal application previously, at this point, your application has been redeployed, and you can visit the page to see a counter. If you open multiple windows, each will be updated simultaneously. If you haven't launched and deployed the application previously do it now. ## Making a persistent sqlite3 database The counter itself needs to be saved in a database, and by default that database is sqlite3 for Rails. For that database to be kept intact across deploys it needs to be placed on a [volume](/volumes). We can create a volume using the CLI: ``` flyctl volumes create sqlite3_volume ``` To use this volume, we are going to need to mount it and set an environment variable relocating the database to be on that volume. We can do both by editing `fly.toml`: ```toml theme={null} [env] DATABASE_URL = "sqlite3:///mnt/volume/production.sqlite3" [mounts] source = "sqlite3_volume" destination = "/mnt/volume" ``` One last problem remains: the `db:prepare` step needs to be run *after* the volume is mounted, which means that it needs to run on the deployment machine, not on the build machine. This means that in your Dockerfile this needs to be done by the CMD statement not on a RUN statement. Start by removing `RUN bin/rails db:prepare`, then replace the CMD statement with: ``` CMD ["/bin/bash", "-c", "bin/rails db:prepare; bin/rails server"] ``` The issue here is that Dockerfiles are limited to one CMD statement. An alternative solution is to create a script (perhaps using a "here" document), and have the CMD run that script. The next time you deploy after this change you will start with a new database. After that, all future deploys will use the same database. Before proceeding to the next step, run `flyctl volumes list` to find the volume id. Delete the volume using `flyctl volumes delete`. Also remove the `DATABASE_URL` from the `[env]` section and delete the `[mounts]` section. ## Changing the database to Postgresql Postgresql is an alternative to sqlite3. To use it, you will need to install a library and a gem: * Add `libpq-dev` to the `apt-get install` line. * Add `--database postgresql` to the `rails new` line. Once again, knowing what libraries to install is a matter of knowing Debian conventions: [libps-dev](https://packages.debian.org/bullseye/libpq-dev). Digging deeper into the [filelist](https://packages.debian.org/bullseye/amd64/libpq-dev/filelist), there is one file, `libpq.so` that needs to be available at deploy time. One approach is to add a copy line: ``` COPY --from=build /usr/lib/x86_64-linux-gnu/ /usr/lib/x86_64-linux-gnu/ ``` Another is to install something that includes it that might be needed anyway at deploy time, such as `postgresql-client`. Since this will be on the deploy image the multi-stage build approach of install and copy only what you need needs to be reversed: install and remove what you don't need. That's why you will often see lines like the following in Dockerfiles: ``` RUN apt-get update -qq && \ apt-get install --no-install-recommends -y \ postgresql-client \ && rm -rf /var/lib/apt/lists /var/cache/apt/archives ``` Now you are ready to create a postgres cluster: ``` flyctl postgres create ``` When done, you will find a link to [Fly Postgres](/unmanaged-postgres). That information may be useful in the future so bookmark the link (or return back to this page), but Fly Postgres is fine for this demo. Your postgres app will be assigned a name if you didn't provide one. Find that name and then run: ``` flyctl postgres attach happy-store-1234 ``` ... replacing the name with the one assigned or selected by you. Before proceeding, there is one more change that might be worth making. If you ever plan to scale up to multiple application machines it would be better to run `db:prepare` exactly once per deploy rather than once per vm. You can accomplish this by adding the following to your `fly.toml`: ``` [deploy] release_command = "bin/rails db:prepare" ``` Then restore the CMD line in your Dockerfile: ``` CMD bin/rails server ``` ## Changing the database to MySQL [MySQL on Fly](/app-guides/mysql-on-fly) is more of a do it yourself kinda thing at the moment. Follow that guide to set up a MySQL application. Once you have it up and running, the instructions are roughly the same as with postgresql, with the following modifications: * On the rails new line, specify `--database mysql` instead of `--database postgresql` * [default-libmysqlclient-dev](https://packages.debian.org/bullseye/default-libmysqlclient-dev) is the package containing the mysql libraries. * [default-mysql-client](https://packages.debian.org/bullseye/default-mysql-client) is the package containing the mysql client. * Add the deploy release command to `fly.toml` from the postgresql instructions above. * You need to run `fly secrets set DATABASE_URL=` in your application. For postgresql this is taken care of for you by the `flyctl postgres attach` command. This will look something like the following: ``` fly secrets set DATABASE_URL=mysql2://root:password@mysql-app-name.internal:3306/ ``` ## Recap * Adding databases require a lot of configuration: * Much of the configuration involve actions outside your Dockerfile: i.e, setting up volumes, secrets, virtual machines, etc. * While Dockerfiles don't reduce the number of steps required, they do provide a place where these actions can be captured concisely and reproducible executed. * Knowing what packages to add and where shared libraries are to be found is once again requires more knowledge of operating system conventions (in this case Debian) than Docker knowledge. # Optimizing your deployment Source: https://docs.fly.io/rails/cookbooks/deploy Tune a deployed Rails app on Fly.io: preload jemalloc for memory allocation, turn on YJIT with RUBY_YJIT_ENABLE, and add swap using swap_size_mb. Before proceeding, you are encouraged to read (or at least skim) the excellent docs Fly.io provides for [Scaling Machine CPU and RAM](/launch/scale-machine). Getting the right sized machine close to your users may be enough. Once you have settled on a machine configuration, there are a number of things you can do to get the most out of that machine. For each item below it is worth benchmarking the results of the change with your application because, as they say, your mileage may vary. ## Jemalloc Memory allocation is key to performance, and [jemalloc](http://jemalloc.net/) is an alternative malloc implementation. Enabling it is a matter of adding the following to your Dockerfile: ``` RUN apt-get install libjemalloc2 ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true ``` ## YJIT A "Just In Time" compiler can make your application run faster. Starting with Ruby 3.1, [YJIT](https://github.com/Shopify/yjit) has been included in the official Ruby release sources. Using it requires a build time option and enabling at runtime. The official Ruby docker images for 3.2.0 and later, as well as [Fullstaq Ruby](https://fullstaqruby.org/), support YJIT and enabling it can be done with an environment variable: ```dockerfile theme={null} ENV RUBY_YJIT_ENABLE=1 ``` Both [rvm](https://rvm.io/) and [rbenv](https://github.com/rbenv/rbenv) will install yjit by default if rustc is available. ## Enabling swap While RAM is always faster, not everything that is loaded into RAM needs equal treatment. Enabling swap can allow less frequently used regions of memory to be moved out to disk (which these days is SSD). See [`swap_size_mb`](/reference/configuration#swap_size_mb-option) for configuring for deploys. ## Recap While the number of "one size fits all" deployment optimizations are few and far between, testing out a new configuration is generally a matter of adding a few lines to a Dockerfile and deploying it. This makes it easy to experiment and find the combination that is right for your application. # Cookbooks Source: https://docs.fly.io/rails/cookbooks/index Rails Dockerfile cookbooks for Fly.io: minimal images, databases, Node.js and API-only apps, plus recipes for copying sources, faster builds and deploys. It is official. Rails 7.1 will have [default dockerfiles](https://github.com/rails/rails/commit/4f3af4a67f227ed7998fed570b9aa671e1b74117). While there is much [polishing](https://community.fly.io/t/preparations-for-rails-7-1/9512) to be done, the direction is clear: going forward, Rails applications will include Dockerfiles. For a large number of people that will move the task of learning Dockerfiles from something that needs to be done someday to something that is important to have at least a working knowledge of relatively soon. There is a lot of good docs out there. A good place to start is with [Rails on Docker](https://fly.io/ruby-dispatch/rails-on-docker/) which will walk you through the current Rails template. Fly Rails cookbooks are for those that wish to explore further. In each, you will start with a minimal Dockerfile that focuses on one topic. And you will be introduced to a small number of new alternatives, exploring different tradeoffs or use cases. While this tutorial is anchored by focusing on deploying Ruby on Rails on fly.io, the concepts apply to other frameworks and other cloud providers. While you are welcome to explore these cookbooks in any order, if the concept of multi-stage docker builds is new to you starting with the first cookbook will make understanding the rest easier. If you are comfortable skipping the first cookbook, all cookbooks start with an empty directory and a seed `Dockerfile`. Run `fly launch` to create an application, and then proceed from there, running `fly deploy` after you make changes. The recipes the follow contain fragments that can be added to multiple cookbooks. For best results, you are encouraged to try out each step. To do so, you will need to [Log in to Fly](/getting-started/sign-up-sign-in). These cookbooks can be more than mere educational materials. Using throwaway applications is often better than experimenting in production when you want to make configuration changes. Starting a minimal application, using `flyctl ssh console` to shell into that machine and explore and make changes, rerunning `fly deploy` to reset the machine when those experiments fail and making changes to your `Dockerfile` and `fly.toml` when things work is an effective way to validate an approach before making changes to your application. Let's get started. ## Cookbooks * [Minimal](./minimal) - full ruby, slim ruby, multi-stage build * [Databases](./databases) - sqlite3, postgresql, mysql * [Node](./node) - support for esbuild, rollup, webpack * [API](./api) - api only rails servers with web front end ## Recipes * [Sources](./sources) - adding sources from outside of your `Dockerfile`. * [Optimizing builds](./build) - ordering, splitting, staging, and caching. * [Optimizing deployments](./deploy) - jemalloc, yjit, swap. A Rails generator incorporating these techniques can be found at [dockerfile-rails](https://github.com/rubys/dockerfile-rails), and is being proposed as the generator to be included by default in Rails 7.1. # Minimal Rails application Source: https://docs.fly.io/rails/cookbooks/minimal Write a minimal Rails Dockerfile and deploy it to Fly.io, then shrink the image from 948MB to 209MB by moving to ruby:slim and a multi-stage build. ## Minimal Rails with Full Ruby Image Create an empty directory, and place the following Dockerfile in there: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby RUN gem install rails RUN rails new demo --minimal --skip-active-record COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF WORKDIR demo ENV RAILS_ENV=production EXPOSE 3000 CMD bin/rails server ``` If you've ever seen a Dockerfile before, the above seems too good to be true. But try it. In your new directory run `flyctl launch`. Accept all the defaults, then run `flyctl deploy`, followed by `flyctl apps open`. Congratulations, you've deployed a Rails app. True, it doesn't do much, but we will add more later. Now let's review that file. The first line is a [syntax directive](https://docs.docker.com/build/buildkit/dockerfile-frontend/). All dockerfiles should have this. In fact, this example wouldn't work without it as it uses here docs. The next line, `FROM ruby`, specifies a base image. A base image contains a completely preconfigured operating system along with selected other packages. You can find the definition for the ruby image on [hub.docker.com](https://hub.docker.com/_/ruby). Next up is two `RUN` statements. The commands you see there should be familiar: `gem install rails` and `rails new demo`. `RUN` statements are run on the build machine. Then comes a `COPY` statement. This statement uses a [heredoc](https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/) which pretty much are the same thing as [heredoc in Ruby](https://www.rubyguides.com/2018/11/ruby-heredoc/). The effect of these three lines will be to replace `config/routes.rb` with a route that shows the Rails welcome page. `WORKDIR` and `ENV` unsurprisingly set the current working directory and an environment variable respectively. The [`EXPOSE 3000`](https://docs.docker.com/engine/reference/builder/#expose) line declares the network port that Rails will listed to at runtime. Finally, there is a `CMD` which is run on the deployed server as opposed to the build server. It is run in the specified WORKDIR with the environment variables you set. EZ-Peasy. If you didn't see it working with your own eyes, it would be hard to imagine it being this straightforward and simple. None of the Dockerfiles you have seen before are as clear as this, so there must be more to this story. ## Minimal Rails with Slim Ruby Image Scrolling by during the build was the size of the image. That size was 948MB. That's compressed. That seems a bit much for a welcome splash screen. Large images take longer to build and longer to deploy. Let's make the image smaller. Returning to [hub.docker.com](https://hub.docker.com/_/ruby), you can see a lot of alternatives. If you scan that page and find the word `slim`, you can click on it and find the Dockerfile that was used to create that image. That image, in turn, is based on `bullseye`, which is the latest release of the [Debian](https://www.debian.org/) distribution of LInux. Knowing this is important. While the full `ruby` image contains everything you *might* need, including plenty that you will never use, `ruby:slim` doesn't contain things that will be needed to generate your demo application. Things like `make` and `git`. Scanning the list of [debian bullseye packages](https://packages.debian.org/stable/allpackages) you can find `build-essential` and `git`. OK, at this point, I'm just kidding. I ran a count and found there to be 58,733 packages on that list. The best way to find out what you need is pasting the error message you find when you don't include what is needed in your favorite search engine and reviewing the answer you find on places like StackOverflow. The way to install packages on Debian is to use a tool named `apt-get`, so when we are done, the `FROM ruby` line gets replaced with the following: ```dockerfile theme={null} FROM ruby:slim RUN apt-get update &&\ apt-get install --yes build-essential git ``` Note that this time we opted to use a single RUN statement, and use the shell `&&` operator to run the second command only if the preceding one completed. This is a common technique you will find in Dockerfiles and it marginally makes the images smaller as two separate steps are combined into one. All in all not *too* bad. Not exactly novice friendly, but still fairly concise. Concise, and effective. With this one change we got the image size down from 948MB to 520MB, a 45% reduction. Not bad. But we can do even better. ## Multi-stage build Build tools are only needed at build time, they don't need to be included in the deployed image. This is accomplished by building two images. In the first one we build the necessary build tools. We then start fresh with a new image, copy over the necessary files from the first image and then proceed on from there. We can use [multi-stage builds](https://docs.docker.com/build/building/multi-stage/) to do that. This is easier than it sounds. You put multiple FROM statements in your Dockerfile, optionally providing aliases to some images with an `as` keyword, and then use the `--from` flag on your COPY commands. Seeing it in action helps. Start by adding `as build` to the existing FROM command, then add the following immediately after the `RUN rails new demo` line: ```dockerfile theme={null} FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle ``` Your deployed application will contain your demo application as well as all of the necessary gems, but none of the build tools used to create the application. The compressed image size is now down to 209MB. While still substantial, this is a dramatic 78% improvement over where we started. At this point you might be wondering where `/usr/local/bundle` came from. Where bundler places installed gems is operating system specific, and can be overridden by setting the `GEM_HOME` environment variable. Bundler has a [docker guide](https://bundler.io/guides/bundler_docker_guide.html#dockerfiles-for-multiple-ruby-applications-and-gems) that provides more information. ## Recap A recap of the progress so far. The entire Dockerfile isn't that big: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby:slim as build RUN apt-get update &&\ apt-get install --yes build-essential git RUN gem install rails RUN rails new demo --minimal --skip-active-record FROM ruby:slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF WORKDIR demo ENV RAILS_ENV=production EXPOSE 3000 CMD bin/rails server ``` A total of 23 lines, 7 of them blank. The docker portions (FROM, RUN, COPY, WORKDIR, ENV, CMD) are very straightforward, even including advanced techniques such as multi-stage builds and here docs. The parts that need explaining are the operating system parts: `apt-get`, `build-essential`, `/usr/local/bundle`, and `3000`. This will remain true as you continue your journey: the hard part isn't learning Docker, but rather getting to know what for many of you is an entirely new operating system. In this case, the operating system is Debian, but Alpine is common, and you will occasionally see member of the RedHat family (Centos, Fedora) as well as others. Docker files make working with these operating systems easy, but do nothing to hide them. To be fair, there still is a bit more to learn about Dockerfiles, but mostly the above statement will continue to be true. # Rails with Node.js Source: https://docs.fly.io/rails/cookbooks/node Four ways to add Node.js to a Rails Docker image on Fly.io, NodeSource, merged images, Volta, or a node base image, compared by size, with an esbuild demo. If your application is based on Rails 5.2 through 6.1, or is a Rails 7 app selecting one of the JavaScript options or many of the css options, you will require Node.js to be included in your deployment image. While it is unsurprising that the slim ruby image doesn't include Nodejs by default, what is surprising is that the most obvious way of installing node and yarn using the Debian packages included with the distribution results in seriously outdated versions being installed. Specifically, nodejs version 12.22.12, and yarnpkg version 1.12.10. Active support for node 12 ended in 2020, and security support ended in 2022. Attempting to run anyway ends up with an error running 'yarn build', first because yarn is called yarnpkg, and second because the generated `package.json` has no build step. Fortunately, there are alternatives. ## Nodesource We can start with the Node.js [recommendation](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions). ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby:slim as base RUN apt-get update &&\ apt-get install --yes git curl build-essential RUN curl -sL https://deb.nodesource.com/setup_current.x | bash - &&\ apt-get update && \ apt-get install --yes --no-install-recommends nodejs &&\ npm install -g yarn FROM base as build RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild FROM base WORKDIR demo COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` Unfortunately this results in a rather chonky image, clocking in at 750MB. And attempting to reduce the size by installing packages such as `build-essentials` later results in failures. ## merging images Another approach is merge files from the official node and ruby images. ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM node:slim AS node FROM ruby:slim as base COPY --from=node /usr/lib /usr/lib COPY --from=node /usr/local/share /usr/local/share COPY --from=node /usr/local/lib /usr/local/lib COPY --from=node /usr/local/include /usr/local/include COPY --from=node /usr/local/bin /usr/local/bin COPY --from=node /opt /opt FROM base as build RUN apt-get update &&\ apt-get install --yes build-essential git RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild FROM base WORKDIR demo COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` The resulting images size is smaller at 465MB. The danger here is that the node and ruby images may not be based on the same image. At the current time both are based on Debian bullseye, but that may not always remain the case. Both [ruby](https://hub.docker.com/_/ruby) and [node](https://hub.docker.com/_/node) provide multiple alternates so it shouldn't be difficult to find a match. ## volta A third approach is to use a version manager. Much like how Ruby has rvm, rbenv, chruby and others, Node has several. [Volta](https://docs.volta.sh/guide/) is one written in Rust that works well for this task. A minimal Dockerfile making use of Volta would look like the following: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby:slim as base ENV VOLTA_HOME=/usr/local FROM base as build RUN apt-get update &&\ apt-get install --yes build-essential git curl RUN curl https://get.volta.sh | bash &&\ volta install node@lts yarn@latest RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild FROM base WORKDIR demo COPY --from=build $VOLTA_HOME/bin $VOLTA_HOME/bin COPY --from=build $VOLTA_HOME/tools $VOLTA_HOME/tools COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` The resulting image is slightly larger at 499 MB. The advantage of this approach is that you don't have to worry about base images matching. ## Node as the base A fourth option is to flip the script: start with node as the base and add ruby. Debian bullseye includes Ruby 2.7 which ended support on March 31, 2023. ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM node:slim AS base RUN apt-get update &&\ apt-get install --yes ruby FROM base as build RUN apt-get install --yes ruby-dev build-essential git RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild FROM base WORKDIR demo COPY --from=build /demo /demo COPY --from=build /var/lib/gems /var/lib/gems COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` Node the change to the second `COPY --from=gems` line as the location of the gem directory has changed. This results in the smallest image yet, at 424 MB. The downside is that you get a dated Ruby. Variations are possible, including copying Ruby from the base docker image or using a Ruby version manager such as [rvm](https://rvm.io/rvm/install), [rbenv](https://github.com/rbenv/rbenv#installation), or [chruby](https://github.com/postmodern/chruby#install). ## Example application Below is an example application that demonstrates React.js working with esbuild. Start by adding the following lines immediately before the `FROM base` line: ``` WORKDIR demo RUN yarn add react react-dom ``` Replace the three lines starting with `COPY <<-"EOF" config/routes.rb` with the following: ```dockerfile theme={null} # syntax = docker/dockerfile:1 FROM ruby:slim as base RUN apt-get update &&\ apt-get install --yes git curl build-essential RUN curl -sL https://deb.nodesource.com/setup_current.x | bash - &&\ apt-get update && \ apt-get install --yes --no-install-recommends nodejs &&\ npm install -g yarn FROM base as build RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild WORKDIR demo RUN yarn add react react-dom FROM base WORKDIR demo COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle RUN bin/rails generate controller Time index RUN cat <<-"EOF" >> app/javascript/application.js import "./components/counter" EOF RUN mkdir app/javascript/components COPY <<-"EOF" app/javascript/components/counter.jsx import React, { useState, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; const Counter = ({ arg }) => { const [count, setCount] = useState(0); const countRef = useRef(count); countRef.current = count; useEffect(() => { const interval = setInterval(() => { setCount(countRef.current + 1); }, 1000); return () => clearInterval(interval); }, []); return
{`${arg} - counter = ${count}!`}
; }; document.addEventListener("DOMContentLoaded", () => { const container = document.getElementById("root"); const root = createRoot(container); root.render(); }); EOF COPY <<-"EOF" app/views/time/index.html.erb
React Logo
ruby=<%= RUBY_VERSION %> rails=<%= Rails::VERSION::STRING %>>
EOF COPY <<-"EOF" config/routes.rb Rails.application.routes.draw { root "time#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` Since this isn't a React or Rails tutorial, an overview of the contents will suffice: * A counter component which extracts attributes from the root element and displays a counter which increments every second. * A time controller and view which includes an SVG image and sets HTML attributes containing the Ruby, Rails, and Node versions. ## Recap There are multiple ways to build an image containing both Node.js and Ruby. Finding the right one for your application requires a bit of trial and error and personal preference. ## Complete examples The variants above are shown in pieces. These two are complete, because they build Ruby from a node base image and the full multi-stage file is hard to follow in fragments. ```docker node plus rbenv/ruby-build theme={null} # syntax = docker/dockerfile:1 FROM node:slim as build RUN apt-get update &&\ apt-get install -y autoconf bison patch build-essential \ libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev \ libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev \ git curl &&\ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y &&\ export PATH=/root/.cargo/bin:$PATH &&\ git clone https://github.com/rbenv/ruby-build.git /opt/ruby-build &&\ /opt/ruby-build/install.sh &&\ ruby-build 3.2.0 /usr/local RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild WORKDIR demo RUN yarn add react react-dom FROM node:slim WORKDIR demo COPY --from=build /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu COPY --from=build /usr/local /usr/local COPY --from=build /demo /demo RUN bin/rails generate controller Time index RUN cat <<-"EOF" >> app/javascript/application.js import "./components/counter" EOF RUN mkdir app/javascript/components COPY <<-"EOF" app/javascript/components/counter.jsx import React, { useState, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; const Counter = ({ arg }) => { const [count, setCount] = useState(0); const countRef = useRef(count); countRef.current = count; useEffect(() => { const interval = setInterval(() => { setCount(countRef.current + 1); }, 1000); return () => clearInterval(interval); }, []); return
{`${arg} - counter = ${count}!`}
; }; document.addEventListener("DOMContentLoaded", () => { const container = document.getElementById("root"); const root = createRoot(container); root.render(); }); EOF COPY <<-"EOF" app/views/time/index.html.erb
React Logo
ruby=<%= RUBY_VERSION %> rails=<%= Rails::VERSION::STRING %>>
EOF COPY <<-"EOF" config/routes.rb Rails.application.routes.draw { root "time#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` ```docker node plus rvm theme={null} # syntax = docker/dockerfile:1 FROM node:slim as build RUN apt-get update && \ apt-get install -y gnupg2 curl procps xz-utils git && \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y &&\ export PATH=/root/.cargo/bin:$PATH &&\ gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB &&\ curl -sSL https://get.rvm.io | bash -s stable && \ bash -c 'source /etc/profile.d/rvm.sh && rvm install 3.2.0' ENV PATH=/usr/local/rvm/wrappers/default:$PATH RUN gem install rails RUN rails new demo --skip-active-record --javascript esbuild WORKDIR demo RUN yarn add react react-dom FROM node:slim WORKDIR demo COPY --from=build /demo /demo COPY --from=build /usr/local/rvm /usr/local/rvm COPY --from=build /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu ENV PATH=/usr/local/rvm/wrappers/default:$PATH RUN bin/rails generate controller Time index RUN cat <<-"EOF" >> app/javascript/application.js import "./components/counter" EOF RUN mkdir app/javascript/components COPY <<-"EOF" app/javascript/components/counter.jsx import React, { useState, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; const Counter = ({ arg }) => { const [count, setCount] = useState(0); const countRef = useRef(count); countRef.current = count; useEffect(() => { const interval = setInterval(() => { setCount(countRef.current + 1); }, 1000); return () => clearInterval(interval); }, []); return
{`${arg} - counter = ${count}!`}
; }; document.addEventListener("DOMContentLoaded", () => { const container = document.getElementById("root"); const root = createRoot(container); root.render(); }); EOF COPY <<-"EOF" app/views/time/index.html.erb
React Logo
ruby=<%= RUBY_VERSION %> rails=<%= Rails::VERSION::STRING %>>
EOF COPY <<-"EOF" config/routes.rb Rails.application.routes.draw { root "time#index" } EOF ENV RAILS_ENV=production RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` # Copy source Source: https://docs.fly.io/rails/cookbooks/sources Copy a Rails project into a Docker image for Fly.io: pin Ruby, Bundler, Yarn and Node versions, add .dockerignore, set RAILS_MASTER_KEY, fix binstubs. In the cookbooks so far, you've seen COPY statements with heredocs, and COPY statements with `--from` options. This page contains a number of recipes which you an add to an existing Dockerfile to incorporate content from outside of the Dockerfile. The way to do this is extremely simple. As an example, the following statements will copy the contents of your current directory (recursively) into the image: ``` COPY . . ``` So you would think that with an existing Rails project you could remove a lot of statements and replace them with a single COPY statement. Generally it turns out that the reverse is true. For starters what once was: ```dockerfile theme={null} RUN gem install rails RUN rails new demo --minimal --skip-active-record ``` Becomes ```dockerfile theme={null} RUN mkdir demo WORKDIR demo COPY . . RUN bundle install RUN yarn install ``` And then even with that change, when you try to deploy you get a message that looks something like this: ``` #14 0.539 Your Ruby version is 3.2.0, but your Gemfile specified 3.1.2 ``` ## Versioning The fix for this is simple. You change your base image. Find occurrences in your Dockerfile that look like: ``` FROM ruby:slim ``` And replace them with ``` FROM ruby:3.1.2-slim ``` And many people will do the following to make it even clearer as to what parts may need to be tailored and what parts are fixed: ``` ARG RUBY_VERSION=3.1.2 FROM ruby:$RUBY_VERSION-slim ``` At this point, your Dockerfile may work, but eagle eyes may spot a warning that looks something like the following: ``` Bundler 2.4.2 is running, but your lockfile was generated with 2.2.33. Installing Bundler 2.2.33 and restarting ``` This means that while the [right thing](https://bundler.io/blog/2022/01/23/bundler-v2-3.html) was eventually done, but your build process could be improved to use the right version in the first place. And while node and yarn aren't as strict or as whiny, installing the versions of these tools that you tested with will minimize surprises. For bundler, the change looks something this: ``` ARG BUNDLER_VERSION=2.2.33 RUN gem install -N bundler -v ${BUNDLER_VERSION} COPY . . RUN bundle _${BUNDLER_VERSION}_ install ``` The process for yarn is similar, and slightly simpler: ``` ARG YARN_VERSION=1.22.19 RUN npm install -g yarn@${YARN_VERSION} ``` For node, four different ways were defined, so there are four different techniques: * nodesource: we can change `setup_current.x` to specify the major node version, for example `setup_18.x` for the current lts version. It isn't possible with this technique to specify the minor or patch version. If this is important to you, chose one of the other three alternatives. * merging images: just as you can specify `FROM ruby:3.2.0-slim` you can specify `FROM node:18.13.0-slim`. It is important to ensure that both images are based on the same operating system (e.g. debian vs alpine), and release (e.g. bullseye vs buster). To make things clearer and avoid problems, include the OS release in the name, for example `ruby:3.2.0-slim-bullseye` and `node:18.13.0-bullseye-slim`. Yes, these two projects do order the parts to this name differently. * volta: specify the node version on the install, for example: `volta install node@18.13.0`. * node as base: specify the desired node version on the FROM line, but then use a Ruby version manager to install the desired version of Ruby. A few complete example Dockerfiles demonstrating these concepts: ```docker minimal/import maps theme={null} # syntax = docker/dockerfile:1 ARG RUBY_VERSION=3.2.0 FROM ruby:$RUBY_VERSION-slim as build ARG BUNDLER_VERSION=2.4.2 RUN apt-get update &&\ apt-get install --yes build-essential git pkg-config RUN mkdir demo WORKDIR demo RUN gem install -N bundler -v ${BUNDLER_VERSION} COPY . . RUN bundle _${BUNDLER_VERSION}_ install FROM ruby:$RUBY_VERSION-slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY <<-"EOF" /demo/config/routes.rb Rails.application.routes.draw { root "rails/welcome#index" } EOF WORKDIR demo ENV RAILS_ENV=production # ENV RAILS_SERVE_STATIC_FILES=true ENV RAILS_LOG_TO_STDOUT=true EXPOSE 3000 CMD bin/rails server ``` ```docker esbuild/volta theme={null} # syntax = docker/dockerfile:1 ARG RUBY_VERSION=3.2.0 FROM ruby:$RUBY_VERSION-slim as base ARG BUNDLER_VERSION=2.4.2 ARG NODE_VERSION=19.3.0 ARG YARN_VERSION=1.22.19 ENV VOLTA_HOME=/usr/local FROM base as build RUN apt-get update &&\ apt-get install --yes build-essential git curl pkg-config RUN curl https://get.volta.sh | bash &&\ volta install node@$NODE_VERSION yarn@$YARN_VERSION RUN mkdir demo WORKDIR demo RUN gem install -N bundler -v ${BUNDLER_VERSION} COPY . . RUN bundle _${BUNDLER_VERSION}_ install RUN yarn install FROM base WORKDIR demo COPY --from=build $VOLTA_HOME/bin $VOLTA_HOME/bin COPY --from=build $VOLTA_HOME/tools $VOLTA_HOME/tools COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle ENV RAILS_ENV=production ENV RAILS_LOG_TO_STDOUT=true # ENV RAILS_SERVE_STATIC_FILES=true RUN bin/rails assets:precompile EXPOSE 3000 CMD bin/rails server ``` ```docker api theme={null} # syntax = docker/dockerfile:1 ARG NODE_VERSION=19.3.0 ARG RUBY_VERSION=3.2.0 FROM node:$NODE_VERSION-slim AS react ARG YARN_VERSION=1.22.19 COPY client client RUN npm install yarn@$YARN_VERSION RUN cd client; yarn build FROM ruby:$RUBY_VERSION-slim AS build ARG BUNDLER_VERSION=2.4.2 RUN apt-get update &&\ apt-get install --yes build-essential git pkg-config RUN mkdir demo WORKDIR demo RUN gem install -N bundler -v ${BUNDLER_VERSION} COPY . . RUN bundle _${BUNDLER_VERSION}_ install FROM ruby:$RUBY_VERSION-slim COPY --from=build /demo /demo COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=react /client/build /demo/public COPY --from=react /client/.node-version /demo WORKDIR demo ENV RAILS_ENV=production ENV RAILS_LOG_TO_STDOUT=true ENV RAILS_SERVE_STATIC_FILES=true EXPOSE 3000 CMD bin/rails server ``` ## dockerignore While `COPY . .` is effective, it is a bit too inclusive. For the same reason that you have a `.gitignore` file you need a `.dockerignore` file: you don't want to deploy your tmp files, your test databases, your node\_modules directory or your master key. If you don't already have one, copy your `.gitignore` file. Now run `fly secrets list`. If you don't see `RAILS_MASTER_KEY` listed there make your master key available to your deployment image by setting a secret. Mac and Linux users can run the following command: ``` fly secrets set RAILS_MASTER_KEY=$(cat config/master.key) ``` Windows users can run the following command in PowerShell: ``` fly secrets set RAILS_MASTER_KEY=$(Get-Content config\master.key) ``` You may already have both the `.dockerignore` file and secret set by this point as `fly launch` will do both for you. ## binstubs At this point you are including all of your source minus binaries, irrelevant files, and sensitive files. There still remains one set of files that aren't binary, are very relevant, but may be platform specific: binstubs. These files start with a [she bang](https://en.wikipedia.org/wiki/Shebang_\(Unix\)), which on Windows machines may contain the letters `.exe`. And consist of multiple lines where the line separator is `\r` on Windows vs `\n` on Linux. And on Linux need to be marked executable in order to run. For these reasons, Windows users may need lines like the following in their Dockerfile: ```dockerfile theme={null} # Adjust binstubs to run on Linux RUN chmod +x /app/bin/* && \ sed -i 's/ruby.exe\r*/ruby/' /app/bin/* && \ sed -i 's/ruby\r*/ruby/' /app/bin/* ``` [chmod](https://en.wikipedia.org/wiki/Chmod) can be used to set permissions and mark files as executable. [sed](https://www.gnu.org/software/sed/manual/sed.html) is a useful tool for making string substitutions in text files. What subset of these lines are needed, or whether these lines are needed at all, can be determined at the time the Dockerfile is generated. ## Recap What started out as a seemingly simple matter - namely copying your source to the deployment machine - turns out to require considerably more thought than most would initially have thought. As with before, Dockerfiles continue provide a concise way to capture these actions and have them executed reproducibly. That being said, there is a subtle shift in the reaction for many when presented with two Dockerfiles: one that does steps you would expect to be needed in a clear and concise way, and another that does steps that you hadn't realized were needed. Task specific documentation like these pages are one way to address this need. Another is tools that not only set up Dockerfiles for you initially but help you maintain them as you iterate on your application. # Dockerfiles and fly.toml Source: https://docs.fly.io/rails/getting-started/dockerfiles Fix common issues after fly launch on Rails: rerun bin/rails generate dockerfile, add packages, put SQLite on a volume, add swap, enable YJIT and caching. Once you have completed running `fly launch` you have some new files, most notably a `Dockerfile` and a `fly.toml` file. For many applications you are ready to deploy. But before you do, scan the following list to see if any of these common situations apply to you, and how to proceed. If after reading this and you still need help, please post on [community.fly.io](https://community.fly.io). We also offer [email support](https://community.fly.io/t/fly-io-support-community-vs-email-read-this-first/9962/1), for the apps you really care about. ## Updates Your application is unlikely to stay the same forever. Perhaps you've updated to a new version of Ruby, bundler, node, or other package. Or added a gem which has system dependencies. When this occurs, you will need to update your Dockerfile to match. In most cases, all you need to do is rerun the generator: ```bash theme={null} bin/rails generate dockerfile ``` > [!NOTE] > > If you get a `Could not find generator 'dockerfile'.` error, your app predates the `dockerfile-rails` gem. Add [dockerfile-rails](https://github.com/rubys/dockerfile-rails) to your Gemfile and run `bundle install` first. The generator will remember the options you selected before (these are stored in `config/dockerfile.yml`). If you need to change a boolean option, add or remove a `no-` prefix before the option name. If you have made hand edits to your Dockerfile you may want to take advantage of the option to diff the changes before they are applied. ## Custom Packages The [Dockerfile generator for Rails](https://github.com/rubys/dockerfile-rails#overview) attempts to detect common dependencies and handle them for you. If you come across a dependency that may be useful to others, please consider opening up an [issue](https://github.com/rubys/dockerfile-rails/issues) or a [pull request](https://github.com/rubys/dockerfile-rails/pulls). You may have needs beyond what is automatically detected. Most [official Ruby docker images](https://hub.docker.com/_/ruby) are based on Debian [bullseye](https://www.debian.org/releases/bullseye/), and there are a [large number of packages](https://packages.debian.org/stable/) available to be installed in this manner. An example adding basic kernel and network monitoring packages from this list: ```bash theme={null} bin/rails generate dockerfile --add procps net-tools traceroute iputils-ping ``` ## Using Sqlite3 Every time you deploy you will start out with a fresh image. If your database is on that image, it too will start fresh which undoubtedly is not what you want. The solution is to create a [Fly Volume](/volumes). Once you have created a volume, you will need to set the `DATABASE_URL` environment variable to cause Rails to put your database on that volume. The result will be the following lines in your `fly.toml` file: ```toml theme={null} [env] DATABASE_URL = "sqlite3:///mnt/volume/production.sqlite3" [mounts] source = "sqlite3_volume" destination = "/mnt/volume" ``` Adjust the name of the source to match the name of the volume you created. ## Out of Memory RAM is a precious commodity - both to those on Hobby plans who want to remain within or near the free allowances, and to apps that want to scale to be able to handle a large number of concurrent connections. Both [fullstaq](https://fullstaqruby.org/) and [jemalloc](https://jemalloc.net/) are used by many to reduce their memory footprint. As every application is different, test your application to see if either are appropriate for you. Enabling one or both can be done by regenerating your Dockerfile and specifying the appropriate option(s): ```bash theme={null} bin/rails generate dockerfile --fullstaq --jemalloc ``` At some point you may find that you need more memory. There are two types: real and virtual. Real is faster, but more expensive. Virtual is slower but free. To scale your app to 1GB of real memory, use: ```bash theme={null} fly scale memory 1024 ``` To allocate 1GB of swap space for use as virtual memory, add the following to your [`fly.toml`](/reference/configuration#swap_size_mb-option): ```bash theme={null} swap_size_mb = 1024 ``` ## Scaling If your application involves multiple servers, potentially spread across a number of regions, you will want to prepare your databases once per deploy not once per server. Regenerate your Dockerfile specifying that you no longer want the prepare step there: ```bash theme={null} bin/rails generate dockerfile --no-prepare ``` Next, add a deploy step to your fly.toml: ```toml theme={null} [deploy] release_command = "bin/rails db:prepare" ``` ## Shelling in Fly provides the ability to `ssh` into your application, and it would be convenient to run things like the Rails console in one line: ```bash theme={null} fly ssh console --pty -C '/rails/bin/rails console' ``` To enable `bin/rails` commands to be run in this manner, adjust your deployed binstubs to set the current working directory: ```bash theme={null} bin/rails generate dockerfile --bin-cd ``` ## Build speeds The Dockerfile you were provided will only install gems and node modules if files like `Gemfile` and `package.json` have been modified. If you are finding that you are doing this often and deploy speed is important to you, turning on build caching can make a big difference. And if your Rails application makes use of node.js, installing gems and node packages in parallel can reduce build time. You can regenerate your Dockerfile to enable one or both: ```bash theme={null} bin/rails generate dockerfile --cache --parallel ``` ## Runtime performance Ruby images starting with 3.2 include [YJIT](https://github.com/Shopify/yjit) but disabled. You can enable YJIT using: ```bash theme={null} bin/rails generate dockerfile --yjit ``` ## Testing Locally If you have Docker installed locally, you can test your applications before you deploy them by running the following commands: ```sh theme={null} bin/rails generate dockerfile --compose export RAILS_MASTER_KEY=$(cat config/master.key) docker compose build docker compose up ``` Windows PowerShell users will want to use the following command instead of export: ```powershell theme={null} $Env:RAILS_MASTER_KEY = Get-Content 'config\master.key' ``` # Deploy an Existing Rails App Source: https://docs.fly.io/rails/getting-started/existing Move an existing Rails app to Fly.io: provision Postgres and Redis, launch with fly launch, deploy, and troubleshoot common issues. If you have an existing Rails app that you want to move over to Fly, this guide walks you through the initial deployment process and shows you techniques you can use to troubleshoot issues you may encounter in a new environment. ## Provision Rails and Postgres Servers To configure and launch your Rails app, you can use `fly launch` and follow the wizard. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Creating app in ~/list Scanning source code Detected a Rails app ? Choose an app name (leave blank to generate one): list ? Select Organization: John Smith (personal) ? Choose a region for deployment: Ashburn, Virginia (US) (iad) Created app list in organization personal Admin URL: https://fly.io/apps/list Hostname: list.fly.dev Set secrets on list: RAILS_MASTER_KEY ? Would you like to set up a Postgresql database now? Yes For pricing information visit: https://fly.io/docs/about/pricing/#postgresql-clu ? Select configuration: Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk Creating postgres cluster in organization personal . . . Postgres cluster list-db is now attached to namelist ? Would you like to set up an Upstash Redis database now? Yes ? Select an Upstash Redis plan Free: 100 MB Max Data Size Your Upstash Redis database namelist-redis is ready. . . . create Dockerfile create .dockerignore create bin/docker-entrypoint create config/dockerfile.yml Wrote config file fly.toml Your Rails app is prepared for deployment. Before proceeding, please review the posted Rails FAQ: https://fly.io/docs/rails/getting-started/dockerfiles/. Once ready: run 'fly deploy' to deploy your Rails app. ``` You can set a name for the app, choose a default region, and choose to launch and attach either or both a PostgreSQL and Redis databases. Be sure to include Redis is if you make use of Action Cable, caching, and popular third-party gems like Sidekiq. ## Deploy your application Deploying your application is done with the following command: ```bash theme={null} fly deploy ``` This will take a few seconds as it uploads your application, builds a machine image, deploys the images, and then monitors to ensure it starts successfully. Once complete visit your app with the following command: ```bash theme={null} fly apps open ``` If all went well, you'll see your Rails application homepage. ## Troubleshooting your initial deployment Since this is an existing Rails app, its highly likely it might not boot because you probably need to configure secrets or other service dependencies. Let's walk through how to troubleshoot these issues so you can get your app running. ### View log files If your application didn't boot on the first deploy, run `fly logs` to see what's going on. ```bash theme={null} fly logs ``` This shows the past few log file entries and tails your production log files. Rails stack tracebacks can be lengthy, and the information you often want to see is at the top. If not enough information is available in the `fly logs` command, try running `fly dashboard`, and select `Monitoring` in the left-hand column. ### Open a Rails console It can be helpful to open a Rails console to run commands and diagnose production issues. If you are not running with a sqlite3 or a volume, the recommended way to do this is to run a [console](/reference/configuration#console-command) in an ephemeral Machine: ```bash theme={null} fly console ``` If you are running with sqlite3 or a volume, you will need to ssh into an existing machine. You may need to first make sure that you have enough [memory](/rails/getting-started/dockerfiles#out-of-memory) to accommodate the additional session. ```bash cmd theme={null} fly ssh console --pty -C "/rails/bin/rails console" ``` ```output output theme={null} Loading production environment (Rails 7.0.4.2) irb(main):001:0> ``` ## Common initial deployment issues Now that you know the basics of troubleshooting production deployments, lets have a look at some common issues people have when migrating their existing Rails applications to Fly. ### Access to Environment Variables at Build Time Some third-party gems and services require configuration including the setting of secrets/environment variables. The `assets:precompile` step will load your configuration and may fail if those secrets aren't set even if they aren't actually used by the `assets:precompile` step. Rails itself has such a variable, and you will see some combination of `SECRET_KEY_BASE` and `DUMMY` in most Dockerfiles. In many cases, you can avoid the problem by adding an `if` statement. For example, if your code looks like: ```ruby theme={null} Stripe.api_key = Rails.application.credentials.stripe[:secret_key] ``` Changing the configuration file to the following will avoid the build time error: ```ruby theme={null} if Rails.application.credentials.stripe Stripe.api_key = Rails.application.credentials.stripe[:secret_key] end ``` If that is not sufficient and you need more such dummy values, add them directly to the `Dockerfile`. Just be sure that any such values you add to your Dockerfile don't contain actual secrets as your Dockerfile will generally be committed to git or otherwise may be visible. If you have need for actual secrets at build time, take a look at [Build Secrets](/apps/build-secrets). Finally, if there are no other options you can generate a Dockerfile that will run `assets:precompile` at deployment time with the following command: ``` bin/rails generate dockerfile --precompile=defer ``` This will result in larger images that are slower to deploy: * The precompile step will be run for each server you deploy rather that once during build time to produce an image that can be deployed multiple times. * Normally Dockerfiles are structured so that packages that are only needed at build time (e.g. Node.js) are not present on the deployed machine.\ If you defer the `assets:precompile` step, these packages will need to be present in order to deploy. If you are evaluating Fly.io for the first time there may be some value in setting precompile to defer initially for evaluation and then work over time to eliminate the issues that prevent you from running this step at build time. Once those issues are resolved, regenerate your Dockerfile using the following command: ``` bin/rails generate dockerfile --precompile=build ``` ### Language Runtime Versions Having different runtime versions of language runtimes on your development machine and on production VMs can lead to problems. Run the following commands to see what versions you are using in development: ```shell theme={null} $ bundle -v $ node -v $ ruby -v $ yarn -v ``` There also are files used by version managers to keep your development environment in sync: `.ruby-version`, and `.node-version`. Finally, `package.json` files may have version numbers in `engines.node` and `packageManager` values. Whenever you update your tools, run the following command to update your Dockerfile: ```shell theme={null} $ bin/rails generate dockerfile ``` You can see the versions of each tool that will be used on your deployment machine by looking for lines that start with `ARG` in your Dockerfile. ### ActiveStorage From the [documentation](https://guides.rubyonrails.org/active_storage_overview.html#what-is-active-storage-questionmark): > Active Storage facilitates uploading files to a cloud storage service like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those files to Active Record objects. It comes with a local disk-based service for development and testing and supports mirroring files to subordinate services for backups and migrations. Accordingly: * Don't use [Disk service](https://community.fly.io/t/active-storage-images-are-not-persisting-in-disk-part-2/13095/3) unless you put the data on a [Volume](/volumes) and are prepared to sync the data between machines * If you want to have your active storage data hosted on fly.io, consider using the [postgres adapter](https://github.com/lsylvester/active_storage-postgresql) or [MinIO](/app-guides/minio). * Of course, you are welcome to use [Amazon S3](https://edgeguides.rubyonrails.org/active_storage_overview.html#s3-service-amazon-s3-and-s3-compatible-apis), [Microsoft Azure](https://edgeguides.rubyonrails.org/active_storage_overview.html#microsoft-azure-storage-service), or [Google Cloud Services](https://edgeguides.rubyonrails.org/active_storage_overview.html#google-cloud-storage-service). [Litefs](/litefs) is currently in beta, and if there were a sqlite3 active storage adapter it could be used for this purpose. If this is of interest, bring up the topic on [community.fly.io](https://community.fly.io/). ### Postgres database drivers If you didn't initially deploy with a postgres database but want to add one later, you can create a database using [`fly postgres create`](/unmanaged-postgres/getting-started/create-pg-cluster). Next, update your dockerfile to include the postgres libraries using: ```shell theme={null} $ bin/rails generate dockerfile --postgresql ``` Finally, attach the database to your application using `fly postgres attach`. Multiple Rails applications can use the same PostgresQL server. Just take care to make sure that each Rails application uses a different database name. ### ActiveSupport::MessageEncryptor::InvalidMessage Generally, this means that there is a problem with your `RAILS_MASTER_KEY`. It is a common initial setup problem, but once it works it tends to keep working. `fly launch` will extract your master key if your project has one and make it available to your deployed application as a [secret](/apps/secrets). If you've already run `fly launch` on a project that doesn't have a master key (commonly because files containing these values are excluded from being pushed by being listed in your `.gitignore` file), you will need to generate a key and set the secret yourself. The [Ruby on Rails Guides](https://guides.rubyonrails.org/security.html#custom-credentials) contains information on generating new credentials. If you've got your app's secrets stored in an encrypted credentials file such as `config/credentials.yml.enc` or `config/credentials/production.yml.enc`, you'll need to provide the master key to your app via `fly secrets set`. For example, if your master key is stored in `config/master.key`, you can run: ```bash theme={null} fly secrets set RAILS_MASTER_KEY=$(cat config/master.key) ``` Windows users can run the following command in PowerShell: ``` fly secrets set RAILS_MASTER_KEY=$(Get-Content config\master.key) ``` You can verify that your credentials are encoded using your current master key using: ```bash theme={null} bin/rails credentials:show ``` You can see what `RAILS_MASTER_KEY` is deployed using: ```bash theme={null} fly ssh console -C 'printenv RAILS_MASTER_KEY' ``` # Experimental Fly Rails Gem Source: https://docs.fly.io/rails/getting-started/fly-rails Deploy Rails to Fly.io with the experimental fly-rails gem: bin/rails fly:launch and fly:deploy, then eject to a fly.toml and Dockerfile for flyctl. This guide runs you through how to use the `fly-rails` gem to deploy your Rails applications to Fly.io using Rails commands. Please note that the `fly-rails` gem is designed to work well with common Rails production configurations, like a Turbo app that uses Sidekiq, AnyCable, Redis, & Postgres. For less common configurations, we recommend using `flyctl` and its respective configuration files to manage your deployment. ## Install the `flyctl` command line interface First you'll need to [install the Fly CLI, and signup for a Fly.io account](/flyctl/install). The gem used in the next step will use this CLI to deploy your application to Fly.io and it's something that's worth learning more about as you become more comfortable with Fly.io. ## Install the gem Add the `fly-rails` gem from the root of your Rails project. ```bash theme={null} bundle add fly-rails ``` This installs and bundles the `fly-rails` gem in your Rails project, which adds commands to Rails that make deploying apps to Fly.io require less steps. ## Launch your app The first deployment will both provision and deploy the application to Fly.io. Run the following command: ```bash theme={null} bin/rails fly:launch ``` If the deployment is successful, you can view your app by running: ```bash theme={null} fly apps open ``` You should see your application! ## Deployments Changes to your application can be deployed with one command. ```bash theme={null} bin/rails fly:deploy ``` This will deploy the latest changes to your application and run any database migrations, if present. ## Ejecting There may be a point where you need to "eject" from the `fly-rails` gem for deployments that require more advanced configuration. Ejecting creates a `fly.toml` and `Dockerfile` at the root of your project that works with `flyctl` and gives you full and exact control over your application configuration. ```bash theme={null} bin/rails generate fly:app --eject ``` # Deploy a Rails App Source: https://docs.fly.io/rails/getting-started/index Deploy a Rails app to Fly.io. Generate a new app, run fly launch to create a Dockerfile and fly.toml, and deploy it in a few minutes. ![](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/rails-intro.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=789fb10e6b0c89329abd968177af0ab2) In this guide we'll develop and deploy a Rails application that first demonstrates a trivial view. In order to start working with Fly.io, you will need `flyctl`, our CLI app for managing apps. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). Once you have logged on, here are the three steps and a recap. ## Rails Splash Screen A newly generated Rails application will display a flashy splash screen when run in development, but will do absolutely nothing in production until you add code. In order to demonstrate deployment of a Rails app on fly, we will create a new application, make a one line change that shows the splash screen even when run in production mode, and deploy the application. ### Create an application Start by verifying that you have Rails installed, and then by creating a new application: ```shell theme={null} $ rails --version $ rails new welcome $ cd welcome ``` Now use your favorite editor to make a one line change to `config/routes.rb`: ```diff theme={null} Rails.application.routes.draw do # Define your application routes per the DSL in # https://guides.rubyonrails.org/routing.html # Reveal health status on /up that returns 200 if the app boots # with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify # that the app is live. get "up" => "rails/health#show", as: :rails_health_check # Defines the root path route ("/") - # root "articles#index" + root "rails/welcome#index" end ``` Now that we have an application that does something, albeit something trivial, let's deploy it. ### Launch To configure and launch the app, you can use `fly launch` and follow the wizard. This demo does not need a database or redis. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Rails app Creating app in ~/tmp/welcome We're about to launch your Rails app on Fly.io. Here's what you're getting: Organization: Jane Developer (fly launch defaults to the personal org) Name: welcome-proud-sun-3423 (generated) Region: Ashburn, Virginia (US) (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: (not requested) Tigris: (not requested) ? Do you want to tweak these settings before proceeding? (y/N) ``` For demo purposes you can accept the defaults. You can always change these later. So respond with "N" (or simply press enter). This will take a few seconds as it uploads your application, builds a machine image, deploys the images, and then monitors to ensure it starts successfully. Once complete visit your app with the following command: ```bash theme={null} fly apps open ``` That's it! You are up and running! Wasn't that easy? ## Arrived at Destination You have successfully built, deployed, and connected to your first Rails application on Fly. We've accomplished a lot with only just one line of code and just one command. Now that you have seen it up and running, a few things are worth noting: * No changes were required to your application to get it to work. * Your application is running on a VM, which starts out based on a docker image. To make things easy, `fly launch` generates a `Dockerfile` and a `bin/docker-entrypoint` for you which you are free to modify. * As your application needs change, you can have us update your `Dockerfiles` for you by running: `bin/rails generate dockerfile`. * There is also a `config/dockerfile.yml` file which keeps track of your dockerfile generation options. This is covered by the [FAQ](/rails/getting-started/dockerfiles). * Other files of note: `.dockerignore` and [`fly.toml`](/reference/configuration), both of which you can also modify. All five files should be checked into your git repository. * `fly dashboard` can be used to monitor and adjust your application. Pretty much anything you can do from the browser window you can also do from the command line using `fly` commands. Try `fly help` to see what you can do. * `fly ssh console` can be used to ssh into your VM. `fly console` can be used to open a rails console. Now that you have seen how to deploy a trivial application, it is time to move on to [The Basics](/rails/the-basics). # Rails on Fly.io Source: https://docs.fly.io/rails/index Run Rails and Hotwire apps on Fly.io servers near your users. Start with the starter app, then The Basics for production and Advanced Guides for scale. ![Picture of a balloon floating through a Rails universe with a Ruby hanging from a single thread](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/rails-intro.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=789fb10e6b0c89329abd968177af0ab2) Fly is a great place to run Rails applications, especially if you plan on running them on multiple servers around the world so your users have a fast, snappy, low-latency experience. This becomes particularly important when deploying a Hotwire Rails app. If you have questions or comments about running Rails applications on Fly.io, create a new topic in [the Fly.io community Forum](https://community.fly.io/) and tag it with "rails" so the right people from Fly.io and our community can give you the support you need. ## [Getting Started](./getting-started) Run through the [Starter Rails App](./getting-started/) guide to get a feel for what it's like to deploy to Fly. If you're short on time, just do the first section and you'll have a Rails app running in just a few minutes. ## [The Basics](./the-basics) Covers everything you need to setup, run, and manage production Rails apps on Fly. These documents also identify some common problems you may encounter and provides some pointers on how to resolve them. ## [Advanced Guides](./advanced-guides) The topics in these documents either apply to a subset of Rails applications or are things you'd do if your application demands scale. # Rails: The Basics Source: https://docs.fly.io/rails/the-basics Rails basics on Fly.io: credentials and secrets, deploys and migrations, Postgres, Redis for Turbo Streams, Tigris for Active Storage, Sidekiq, consoles. Most Rails applications require additional setup beyond provisioning a Postgres and Redis database. These guides will help you get through the basics of setting up your Rails application with pieces of infrastructure commonly found in medium-to-high complexity Rails applications. ## Environment Configuration Add environment variables to your Rails applications, configure secrets, and use the encrypted `credentials.yml` file to manage your application’s configuration on Fly. ## Deployments Understand what it means to deploy a Rails application to Fly along with some common tasks you may want to run after deployments, like a database migration or script. ## Active Record Provision an application using a Postgres database ## Turbo Streams & Action Cable Provision a Redis Server and configure Rails to run Turbo Streams. ## Active Storage Provision a Tigris Bucket and configure Rails to run Active Storage. ## Sidekiq Background Workers Deploy Rails applications that run in multiple processes to one Fly application, like Sidekiq background jobs. ## Running Tasks & Consoles Access the Rails console, run rake tasks, and access the SSH shell of a running Rails application with these one-liners. # Active Record Source: https://docs.fly.io/rails/the-basics/active-record Build a Rails app with a scaffolded Postgres table and deploy it to Fly.io with fly launch, which detects the app needs Postgres and provisions it. ![](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/rails-intro.webp?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=789fb10e6b0c89329abd968177af0ab2) In this guide we'll develop and deploy a Rails application that scaffolds a database table. ## Create an application Start by verifying that you have Rails installed, and then by creating a new application: ```shell theme={null} $ rails --version $ rails new list --database postgresql $ cd list ``` ## Scaffold to Success Real Rails applications store data in databases, and Rails scaffolding makes it easy to get started. We are going to start with the simplest table possible, add a small bit of CSS to make the display a bit less ugly, and finally adjust our routes so that the main page is the index page for our new table. ### Scaffold and style a list of names Since we are focusing on fly deployment rather than Rails features, we will keep it simple and create a single table with exactly one column: ```bash theme={null} bin/rails generate scaffold Name name ``` Now add the following to the bottom of `app/assets/stylesheets/application.css`: ```css theme={null} #names { display: grid; grid-template-columns: 1fr max-content; margin: 1em; } #names strong { display: none; } #names p { margin: 0.2em; } ``` And finally, as the splash screen served it purpose, edit `config/routes.rb`, and connect the root with the names index: ```diff theme={null} Rails.application.routes.draw do # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") - root "rails/welcome#index" + root "names#index" end ``` While certainly not fancy or extensive, we now have an application that makes use of a database. Let's deploy it. ### Launch To configure and launch the app, you can use `fly launch` and follow the wizard. Fly.io will automatically detect that Postgres is required by your application. ```bash cmd theme={null} fly launch ``` ```output output theme={null} Detected a Rails app Creating app in ~/tmp/list We're about to launch your Rails app on Fly.io. Here's what you're getting: Organization: Joe Developer (fly launch defaults to the personal org) Name: list-little-fire-1088 (generated) Region: Ashburn, Virginia (US) (this is the fastest region for you) App Machines: shared-cpu-1x, 1GB RAM (most apps need about 1GB of RAM) Postgres: (Fly Postgres) 1 Node, shared-cpu-1x, 256MB RAM, 1GB disk (determined from app source) Redis: (not requested) Tigris: (not requested) ? Do you want to tweak these settings before proceeding? (y/N) ``` For demo purposes you can accept the defaults. You can always change these later. So respond with "N" (or simply press enter). This will take a few seconds as it uploads your application, builds a machine image, deploys the images, and then monitors to ensure it starts successfully. Once complete visit your app with the following command: ```bash theme={null} fly apps open ``` That's it! You are up and running! Wasn't that easy? # Active Storage Source: https://docs.fly.io/rails/the-basics/active-storage Set up Rails Active Storage on Fly.io with a Tigris bucket, automatically through fly launch or with fly storage create and the dockerfile-rails generator. Fly.io is a great place to deploy Rails applications that make use of [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html), and Fly.io knows that Rails users prefer Convention over Configuration and their menus Omakase. ## Launching a new Application? If any of the following are true, [`fly launch`](/flyctl/cmd/fly_launch) will take care of all of the configuration: * `aws-sdk-s3` in `Gemfile` or `Gemfile.lock` * any migration containing `active_storage_attachments` * an uncommented line in `config/storage.yml` containing `service: S3` Don't want Tigris for any reason? Hey - we don't judge here. Simply disable the extension in the web UI, and [setup](https://edgeguides.rubyonrails.org/active_storage_overview.html#setup) active storage for yourself. ![Tigris Launch-UI](https://mintcdn.com/fly-io/uE4KNLArpeLTM_fo/images/tigris-launch-ui.png?fit=max\&auto=format\&n=uE4KNLArpeLTM_fo\&q=85\&s=5c791fa3e123adacd987604e64ef6426) ## Adding Tigris to an existing application? No problem! It can be as easy as a two step process. Step 1: Create a storage bucket: ```bash cmd theme={null} fly storage create ``` ```output output theme={null} ? Choose a name, use the default, or leave blank to generate one: Your Tigris project (xxx) is ready. See details and next steps with: https://fly.io/docs/tigris/ Setting the following secrets on xxx: AWS_ACCESS_KEY_ID: tid_xxx AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev AWS_REGION: auto AWS_SECRET_ACCESS_KEY: tsec_xxxxx BUCKET_NAME: xxx Secrets are staged for the first deployment ``` Step 2: Let [`dockerfile-rails`](https://github.com/fly-apps/dockerfile-rails?tab=readme-ov-file#overview) do the configuring for you: ``` bin/rails generate dockerfile --tigris ``` Note: you don't need to accept changes to your `Dockerfile`, `.dockerignore`, `bin/docker-entrypoint` or other files. The only files that need to be updated are: * `config/storage.yml` * `config/environments/production.rb` ## Want a demo? Following is a quick and dirty demo that enables you to upload files containing images, audio, video, and other assorted files for viewing and/or downloading. First, some scaffolding: ``` rails new filelist --css tailwind cd filelist bin/rails active_storage:install bin/rails generate scaffold Item name:string contents:attachment bin/rails db:migrate ``` Next we need to modify three files to complete the application. * [diffs](https://gist.github.com/rubys/20ea2562b9e7d23e3f01a6852f30d731) * [full files](https://gist.github.com/rubys/c9f0e28727b58365477ce2b858f26355) With this in place, you are ready to launch: ```bash theme={null} fly launch ``` Watch the app deploy and then upload, view, and download a few files! ## Find out more! Now that you are up and running, there is a lot more to explore on the [Tigris Global Object Storage](/tigris) page. Highlights include public buckets, migrating to Tigris with shadow buckets, Pricing, and AWS API compatibility. # Environment Configuration Source: https://docs.fly.io/rails/the-basics/configuration Configure a Rails app on Fly.io with secrets, non-sensitive env vars in fly.toml, and encrypted credentials files unlocked by setting RAILS_MASTER_KEY. Rails applications are usually configured via the encrypted `credentials.yml` file or via environmental variables. ## Environmental variables Environment variables are a great way to configure a Rails application that needs to run in multiple environments. ### Secret variables Environment variables that have sensitive data in them, like a `DATABASE_URL` that contains a password, can be kept in a secret that can't be viewed except when the container is running. To set a secret in Fly, run: ```bash theme={null} fly secrets set SUPER_SECRET_KEY=password1234 ``` ### Non-sensitive variables Variables that don't have sensitive data can be set in the `fly.toml` file under the `[env]` directive. An example file might look like: ```toml theme={null} [env] RAILS_LOG_TO_STDOUT = x ``` ### View the variables To view the environment variables of your Fly Rails app, run: ```bash theme={null} fly ssh console -C "printenv" ``` There you'll see all of the environment variables in your application that have been set by `fly secrets`, the `[env]` directive in the `fly.toml` file, and the `environment` directive from your Dockerfile. ## Encrypted credentials file Another approach to managing credentials in Rails is to use an encrypted credentials file, such as `config/credentials.yml.enc` or `config/credentials/production.yml.enc`, which you can learn more about by running the following from the root of your Rails application: ```bash theme={null} bin/rails credentials:help ``` When deploying to production, the `RAILS_MASTER_KEY` that will decrypt the credentials file can be set via `fly secrets set`. For example, if your master key is stored in `config/master.key`, you can run: ```bash theme={null} fly secrets set RAILS_MASTER_KEY=$(cat config/master.key) ``` For Windows/PowerShell: ```powershell theme={null} $Env:RAILS_MASTER_KEY = Get-Content 'config\master.key' ``` # Deployments Source: https://docs.fly.io/rails/the-basics/deployments Deploy a Rails app to Fly.io with fly deploy, view release history, debug failed deploys with fly logs, and control how database migrations run on deploy. Deploying applications to Fly can be as simple as running: ```bash theme={null} fly deploy ``` When the application successfully deploys, you can quickly open it in the browser by running: ```bash theme={null} fly apps open ``` If all goes well, you should see a running application in your web browser. You can also view a history of deployments by running: ```bash cmd theme={null} fly releases ``` ```output output theme={null} VERSION STABLE TYPE STATUS DESCRIPTION USER DATE v55 true scale succeeded Scale VM count: ["web, 6"] brad@fly.io 2022-08-10T17:05:57Z v54 true scale dead Scale VM count: ["web, 0"] brad@fly.io 2022-08-10T16:43:13Z v53 true scale succeeded Scale VM count: ["web, 6"] brad@fly.io 2022-08-10T16:42:51Z v52 true scale succeeded Scale VM count: ["web, 6"] brad@fly.io 2022-08-10T16:40:57Z v51 true scale succeeded Scale VM count: ["web, 3"] kurt@fly.io 2022-08-08T20:14:08Z v50 true scale succeeded Scale VM count: ["web, 3"] kurt@fly.io 2022-08-08T19:55:23Z ``` ## Troubleshooting a deployment If a deployment fails, you'll see an error message in the console. If the error is a Rails stack trace, it will be truncated. To view the entire error message run: ```bash theme={null} fly logs ``` You may need to open another terminal window and deploy again while running `fly logs` to see the full error message. ## Running migrations For Postgresql, migrations are configured to automatically run after each deployment via the following task in your application's `fly.toml`: ```toml theme={null} [deploy] release_command = './bin/rails db:prepare' ``` Sqlite3 migrations are done by the `bin/docker-entrypoint` script. To disable automatic migrations for deploys, remove `db:prepare` lines from these files. Then, to manually run migrations after a deployment, run: ```bash theme={null} fly ssh console -C "/rails/bin/rails db:migrate" ``` ## Run ad-hoc tasks after deploying Sometimes after a deployment you'll need to run a script or migration in production. That can be accomplished with the Fly SSH console by running: ```bash cmd theme={null} fly ssh console ``` ```output output theme={null} Connecting to top1.nearest.of.my-rails-app.internal... complete ``` ```bash cmd theme={null} ls ``` ```output output theme={null} Dockerfile README.md config lib test Gemfile Rakefile config.ru log tmp Gemfile.lock app db public vendor Procfile.dev bin fly.toml storage ``` ```bash cmd theme={null} bundle exec ruby my-hello-world-script.rb ``` ```output output theme={null} hello world ``` # Running Tasks & Consoles Source: https://docs.fly.io/rails/the-basics/run-tasks-and-consoles Open a Rails console, an interactive shell, or run rake and custom Rails tasks on a production app on Fly.io using fly ssh console with the pty flag. ## Rails console To access an interactive Rails console, run: ```bash cmd theme={null} fly ssh console --pty -C "/rails/bin/rails console" ``` ```output output theme={null} Loading production environment (Rails 7.0.4.2) irb(main):001:0> ``` Then start using the console, but be careful! You're in a production environment. > [!NOTE] > > The `--pty` flag tells the SSH server to run the command in a pseudo-terminal. You will generally need this only when running interactive commands, like the Rails console. > [!NOTE] > > If you get a `no such file or directory` error, your app may not be installed in the `/rails` directory. SSH in with `fly ssh console` and check your app's location, then adjust the path accordingly. Apps built with the [dockerfile generator](https://github.com/fly-apps/dockerfile-rails) use `/rails` as the working directory by default. You may also need to [set up your binstubs](#rails-tasks) with `bin/rails generate dockerfile --bin-cd`. ## Interactive shell To access an interactive shell as the `root` user, run: ```bash cmd theme={null} fly ssh console --pty -C '/bin/bash' ``` ```output output theme={null} # ``` To access an interactive shell as the `rails` user, requires a tiny bit of setup: ```bash theme={null} bin/rails generate dockerfile --sudo ``` Accept the changes to your Dockerfile, and then rerun `fly deploy`. Once this is complete, you can create an interactive shell: ```bash cmd theme={null} fly ssh console --pty -C 'sudo -iu rails' ``` ```output output theme={null} $ ``` ## Rails tasks In order to run other Rails tasks, a small change is needed to your Rails binstubs to set the current working directory. The following command will make the adjustment for you: ```bash theme={null} bin/rails generate dockerfile --bin-cd ``` Accept the changes to your Dockerfile, and then rerun `fly deploy`. Once this is complete, you can execute other commands on Fly, for example: ```bash theme={null} fly ssh console -C "/rails/bin/rails db:migrate" ``` To list all the available tasks, run: ```bash theme={null} fly ssh console -C "/rails/bin/rails help" ``` ## Custom Rake tasks You can create [Custom Rake Tasks](https://community.fly.io/) to automate frequently used commands. As an example, add the following into `lib/tasks/fly.rake` to reduce the number of keystrokes it takes to launch a console: ```ruby theme={null} namespace :fly do task :ssh do sh 'fly ssh console --pty -C "sudo -iu rails"' end task :console do sh 'fly ssh console --pty -C "/rails/bin/rails console"' end task :dbconsole do sh 'fly ssh console --pty -C "/rails/bin/rails dbconsole"' end end ``` You can run these tasks with `bin/rails fly:ssh`, `bin/rails fly:console`, and `bin/rails fly:dbconsole` respectively. # Sidekiq Background Workers Source: https://docs.fly.io/rails/the-basics/sidekiq Run Sidekiq background workers for a Rails app on Fly.io as a separate process group in fly.toml, backed by Redis, and scale app and worker counts. Rails applications commonly defer complex tasks that take a long to complete to a background worker to make web responses seem fast. This guide shows how to use [Sidekiq](https://github.com/mperham/sidekiq), a popular open-source Rails background job framework, to set up background workers, but it could be done with other great libraries like [Good Job](https://github.com/bensheldon/good_job), [Resque](https://github.com/resque/resque), [etc](https://www.ruby-toolbox.com/categories/Background_Jobs). ## Provision a Redis server Sidekiq depends on Redis to communicate between the Rails server process and the background workers. Follow the [Redis setup guide](/upstash/redis) to provision a Redis server and set a `REDIS_URL` within the Rails app. Be sure to set the `REDIS_URL` via a secret as demonstrated [here](/rails/the-basics/configuration#secret-variables). Verify the `REDIS_URL` is available to your Rails application before you continue by running: ```bash cmd theme={null} fly ssh console -C "printenv REDIS_URL" ``` ```output output theme={null} REDIS_URL=redis://default:yoursecretpassword@my-apps-redis-host.internal:6379 ``` If you don't see `REDIS_URL` in the command above, Sidekiq won't be able to connect and process background jobs. ## Run multiple processes Most production Rails applications run background workers in a separate process. There's a few ways of accomplishing that on Fly that are [outlined in the multiple-processes](/app-guides/multiple-processes) docs. The quickest way to run multiple processes in one region is via the `processes` directive in the `fly.toml` file. > [!NOTE] > > The `[processes]` directive currently only works within a single Fly region. [Scaling a Rails application to multiple regions](/rails/advanced-guides/multi-region) requires a different approach to running multiple processes. Add the following to the `fly.toml`: ```toml theme={null} [processes] app = "bin/rails server" worker = "bundle exec sidekiq" ``` Then under the `[http_service]` directive, add `processes = ["app"]`. The configuration file should look something like this: ```toml theme={null} [http_service] processes = ["app"] # this service only applies to the app process internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true min_machines_running = 0 ``` This associates the process with the service that Fly launches. ## Deploy and test Once multiple processes are configured in the `fly.toml` file, deploy them via: ```bash theme={null} fly deploy ``` If all goes well the application should launch with both `app` and `worker` processes. Be sure to run through the application and test features that kick-off background jobs. If you're having issues getting it working, run `fly logs` to see errors. ## Scaling Scaling up and down processes may be accomplished by running: ```bash theme={null} fly scale count app=3 worker=3 ``` To view the current state of the application's scale, run: ```bash cmd theme={null} fly status ``` ```output output theme={null} App Name = my-rails-app Owner = personal Version = 41 Status = running Hostname = my-rails-app.fly.dev Instances ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED 15088508 worker 41 ord run running 0 34s ago 8789ef49 app 41 ord run running 1 total, 1 passing 0 2022-07-26T16:06:34Z c419942b app 41 ord run running 1 total, 1 passing 0 2022-07-26T16:05:52Z ea7af986 app 41 ord run running 1 total, 1 passing 0 2022-07-26T16:05:52Z d681c33d worker 41 ord run running 0 2022-07-26T15:42:30Z d8d8dc08 worker 41 ord run running 0 2022-07-26T15:42:30Z ``` In this case, we can see that 3 worker processes and 3 app processes are running in the `ord` region. # Turbo Streams & Action Cable Source: https://docs.fly.io/rails/the-basics/turbo-streams-and-action-cable Deploy Rails Turbo Streams and Action Cable on Fly.io by creating an Upstash Redis database, setting REDIS_URL, and testing live updates across browsers. Deploying Turbo Streams or Action Cable with a Rails app involves provisioning a redis cluster and a few updates to your application. ## Provisioning Redis Before proceeding, verify that your application is already set up to use Redis. Examine your `Gemfile` and look for the following lines: ```ruby theme={null} # Use Redis adapter to run Action Cable in production gem "redis", "~> 4.0" ``` If the second line is commented out, uncomment it and then run `bundle install`. Rails will automatically have done this for you if it detected the `redis-server` executable on your machine at that time the application was created. Now that Rails is ready to make use of Redis, lets deploy a redis cluster: ```bash cmd theme={null} fly redis create ``` ```output output theme={null} ? Select Organization: John Smith (personal) ? Choose a Redis database name (leave blank to generate one): list-redis ? Choose a primary region (can't be changed later) Ashburn, Virginia (US) (iad) ? Optionally, choose one or more replica regions (can be changed later): Upstash Redis can evict objects when memory is full. This is useful when caching in Redis. This setting can be changed later. Learn more at https://fly.io/docs/upstash/redis/#memory-limits-and-object-eviction-policies ? Would you like to enable eviction? No ? Select an Upstash Redis plan Free: 100 MB Max Data Size Your Upstash Redis database list-redis is ready. Apps in the personal org can connect to at redis://default:.upstash.io If you have redis-cli installed, use fly redis connect to connect to your database. ``` Once again, you can set a name for the database, chose a primary region as well as a number of replica regions, enable eviction, and select a plan. The most important line in this output is the second to the last one which will contain a URL starting with `redis:`. The URL you see will be considerably longer than the one you see above. You will need to provide this URL to Rails, and with Fly.io this is done via [secrets](/apps/secrets). Run the following command replacing the url with the one from the output above: ```bash theme={null} fly secrets set REDIS_URL=redis://default:.upstash.io ``` Now you are ready. Rails is set up to use redis, knows where to find the redis instance, and the instance is deployed. Now onto the implementation: ## Adding Turbo Streams to your Application There are very few steps to make this work, writing very few lines of code Let's start with the `turbo_stream_for` helper, which under the hood uses `Turbo::StreamsChannel`. Modify `app/views/names/index.html.erb` to stream from "names": ```diff theme={null}

<%= notice %>

Names

+ + <%= turbo_stream_from 'names' %>
<% @names.each do |name| %> <%= render name %>

<%= link_to "Show this name", name %>

<% end %>
<%= link_to "New name", new_name_path %> ``` And we complete the client changes by modifying `app/views/names/_name.html.erb` to identify the turbo frame: ```diff theme={null} -
+ <%= turbo_frame_tag(dom_id name) do %>

Name: <%= name.name %>

-
+ <% end %> ``` There is only one step left, and that is to modify `app/controllers/names_controller.rb` to broadcast changes as updates are made: ```diff theme={null} # PATCH/PUT /names/1 or /names/1.json def update respond_to do |format| if @name.update(name_params) format.html { redirect_to name_url(@name), notice: "Name was successfully updated." } format.json { render :show, status: :ok, location: @name } + + @name.broadcast_replace_later_to 'names', partial: 'names/name' else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @name.errors, status: :unprocessable_entity } end end end ``` ## Deployment and testing By now it should be no surprise that deployment is as easy as `fly deploy` and `fly apps open`. Once that is done, copy the browser URL, open a second browser window (it can even be a different browser or even on a different machine), and paste the URL into the new window. With one browser window open to the index page, use the other browser to change one of the names. Once you click "Update name" the index list in the original window will instantly update. Of course, if this were a real application, inserting and removing names would cause those changes to be broadcast. As they say, this is left as an exercise for the student. ## Arrived at Destination You have successfully built, deployed, and connected to your first Rails application on Fly.io. We've accomplished a lot with only just over a handful of lines of code and just over a dozen commands. # The Fly.io Architecture Source: https://docs.fly.io/reference/architecture See how Fly.io runs your code in Firecracker microVMs on dedicated servers and routes traffic with BGP Anycast, fly-proxy, and WireGuard backhaul. ## Compute Application code runs in [Firecracker microVMs](https://github.com/firecracker-microvm/firecracker). These are lightweight, secure virtual machines based on strong hardware virtualization. Your workloads are safely isolated no matter where they’re running on our infrastructure. MicroVMs provide strong hardware-virtualization-based security and workload isolation, this allows us to safely run applications from different customers on shared hardware. We make a best-effort attempt to dedicate hardware resources to only one microVM at a time. CPU cores, for instance, should only ever be doing work for one microVM so your apps don't have to contend with `steal`. The virtualized applications run on dedicated physical servers with 8-32 physical CPU cores and 32-256GB of RAM. ## Fly.io networking ### BGP Anycast We broadcast and accept traffic from ranges of IP addresses (both IPv4 and IPv6) in all our datacenters. When we receive a connection on one of those IPs, we match it back to an active customer application, and then proxy the TCP connection to the closest available microVM. ### Fly Proxy Every server in our infrastructure runs a Rust-based proxy named `fly-proxy`. The proxy is responsible for accepting client connections, matching them to customer applications, applying [handlers](/networking/services#connection-handlers) (eg: TLS termination), and [backhaul](#backhaul) between servers. For more information, see [Fly Proxy](/reference/fly-proxy). ### Backhaul If you have users in Dallas, and an available MicroVM in Chicago, we will accept traffic in Dallas, terminate TLS (unless you've disabled that handler), and then connect to your MicroVM over a WireGuard tunnel between datacenters. WireGuard allows us to pass along almost any kind of network connection with very little additional latency. # Autoscaling Source: https://docs.fly.io/reference/autoscaling Compare the two ways to autoscale Machines on Fly.io: Fly Proxy autostop/autostart for an existing pool, or a metrics-based autoscaler you deploy as an app. ![Illustration by Annie Ruygt of several machines running](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/autoscaling.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=89df8e7d008844ee4ac8d08584e88ab8) Autoscaling adjusts the number of running or created Fly Machines dynamically. We support two forms of autoscaling: * Autostop/autostart Machines * Metrics-based autoscaling ## Autostop/autostart Machines Fly Proxy autostop/autostart starts and stops Machines based on load; Machines are never created or deleted. You create a "pool" of Machines in one or more regions and Fly Proxy starts and stops them as needed. For details about how it works, see [Fly Proxy autostop/autostart](/reference/fly-proxy-autostop-autostart). To configure autostop/autostart for your app, see [Autostop/autostart Machines](/launch/autostop-autostart). ## Metrics-based autoscaling The metrics-based autoscaler scales your application based on any metric. You deploy the autoscaler as an app in your organization. It polls collected metrics and computes the number of machines needed based on the metric you define. The autoscaler can create and delete machines or stop and start existing Machines. To learn how to deploy and configure the autoscaler, see [Autoscale based on metrics](/launch/autoscale-by-metric). # Migrating from AWS to Fly.io Overview Source: https://docs.fly.io/reference/aws-to-fly-guide Moving from AWS to Fly.io: how EC2, EBS, S3, VPCs, RDS, and CloudWatch map to Fly Machines, Volumes, Tigris, 6PN, Managed Postgres, and Grafana. ![Illustration by Annie Ruygt of a figure jumping from one mountain to another](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/migrating-from-aws.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=805c99c4914a71cef1ff3c36c73019bb) > [!NOTE] > > **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](/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](/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](/tigris). It's S3-compatible and runs on Fly.io itself. If you're migrating from AWS S3, there's a handy [shadow bucket](/tigris#migrate-from-another-s3-compatible-provider-with-zero-downtime) 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](/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](/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](/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](/blueprints/infra-automation-without-terraform). On the plus side, deployments can be [zero-downtime](/blueprints/seamless-deployments) if you use [health checks](/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](/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](/postgres), 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](/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](/apps/overview) * [Fly Volumes](/volumes/overview) * [Managed Postgres](/postgres) * [Going to Production Checklist](/apps/going-to-production) # Builders and Fly.io Source: https://docs.fly.io/reference/builders Three ways Fly.io builds a deployable image: the default Dockerfile builder, Cloud Native Buildpacks from Paketo or Heroku, or a prebuilt image. When you deploy an app on Fly.io, the app has to be assembled into a deployable image. That's the job that builders take on. There are three kinds of Fly builders - **dockerfile**, **buildpacks**, and **image**. ## Dockerfile The dockerfile builder is the default builder, invoked when there is a `Dockerfile` and there are no build settings in the `fly.toml` file. It looks for a `Dockerfile` in the current directory and uses that to construct the deployable image. If you are familiar with Docker, you'll be at home with this option. This is the most flexible of the options, but with that flexibility comes the need to write Dockerfiles and the associated quirks of the Docker build system. Which is why we have further build options on Fly that simplify the process. ## Buildpacks Platforms like Heroku and Cloud Foundry have the idea of a buildpack, a building process that's run entirely in its own container, to construct their deployable images. These buildpacks are then bundled into a "builder" stack with an operating system and can be called upon to build an app. The buildpack idea has been standardized with [Cloud Native Buildpacks](https://buildpacks.io/). Buildpacks use several tests to detect if they can build the application and if they can, then proceed to run the scripts needed to create an image. A library of standardized buildpacks is available from [Paketo Buildpacks](https://paketo.io/) and it's from this library, Heroku's Heroku20 buildpack, and Fly's own buildpack (for Deno), that you can select from in flyctl. If you want to use an unlisted buildpack, you can specify it by name using the `buildpacks` setting in `fly.toml`. Buildpack configuration options - such as `YARN_PRODUCTION` in the [Heroku Nodejs Buildpack](https://devcenter.heroku.com/articles/nodejs-support#using-npm-install) - can be set via [Docker build arguments](/reference/configuration#specify-docker-build-arguments). The deploy process works the same way with buildpacks. ## Image Finally, if you already have a Docker image in a repository and just want to deploy that, you can skip the building process and go straight to the deploy with the image build option. # App configuration (fly.toml) Source: https://docs.fly.io/reference/configuration Configure a Fly.io app in fly.toml: build settings, deploy strategy, env vars, http_service and services, mounts, VM size, checks, processes, statics and files. You can configure an app for deployment on Fly.io using a `fly.toml` file. Configuration of builds, environment variables, internet-exposed services, disk mounts, and release commands go here. `fly.toml` configuration applies for apps managed by [Fly Launch commands](/launch/index). ## App configuration files Most of the time you'll use the `fly.toml` file that you get automatically when you run `fly launch` to create an app. There are also ways to re-use config files and to generate them in different formats. ### Ways to get a config file * Run [`flyctl launch`](/flyctl/cmd/fly_launch) in your project source directory to create a new app and `fly.toml` file automatically. * Run [`flyctl config save -a `](/flyctl/cmd/fly_config_save) to download a `fly.toml` file from an existing app. * Create one by hand with help from this reference, or copy and modify an example file. ### Config file formats The default configuration file generated by Fly Launch is called `fly.toml`. TOML is a simple [configuration file format](https://github.com/toml-lang/toml). If you're a VSCode user, then you can install the [Even Better TOML](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) extension for automatic `fly.toml` validation and hints drawn from this documentation. If you want your config file in JSON or YAML format, use the `--json` and `--yaml` options in flyctl commands: * Add the `--yaml` or `--json` options to `fly launch`. * For existing apps, run `fly config save -a [!WARNING] > > 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: ```toml theme={null} 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`). 2. It waits for up to `kill_timeout` seconds. 3. 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](/flyctl/cmd/fly_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. Here's an example of a console command for Django: ```toml theme={null} console_command = "/code/manage.py shell" ``` ## `swap_size_mb` option Setting this option enables Linux swap on Machines. A swap partition is created with size `swap_size_mb`, expressed in megabytes. Swapping to disk can help avoid out-of-memory crashes on brief spikes in memory use. Swap is much slower than RAM, so if performance is important, a better solution is to increase the Machine memory with [`fly scale memory`](/flyctl/cmd/fly_scale_memory). ```toml theme={null} swap_size_mb = 512 ``` ## The `build` section The optional build section contains key/values concerned with how the application should be built. You can read more about builders in [Builders and Fly](/reference/builders) ### builder ```toml theme={null} [build] builder = "paketobuildpacks/builder-jammy-base" ``` The builder "builder" uses CNB Buildpacks and Builders to create the application image. These are third party toolkits which can use Heroku compatible build processes or other tools. The tooling is all managed by the buildpacks and buildpacks are assembled into CNB Builders - images complete with the buildpacks and OS to run the tool chains. In our example above, the builder is being set to use [Paketo's all-purpose builder](https://paketo.io) which automatically detects needed buildpacks. You can also explicitly define buildpacks: ```toml theme={null} [build] builder = "paketobuildpacks/builder-jammy-base" buildpacks = ["docker.io/paketobuildpacks/nodejs"] ``` ### Specify a Docker image ```toml theme={null} [build] image = "flyio/hellofly:latest" ``` The image builder is used when you want to immediately deploy an existing public image. When deployed, there will be no build process; the image will be prepared and uploaded to Fly.io infrastructure as is. This option is useful if you already have a working Docker image you want to deploy on Fly.io or you want a well known Docker image from a repository to be run. ### Specify a Dockerfile ```toml theme={null} [build] dockerfile = "Dockerfile.test" ``` `dockerfile` accepts a relative path to a Dockerfile, or a URL. By default, `flyctl` looks for `Dockerfile` in the application root. > [!NOTE] > > **Gotchas:** > > * This option will not change the Docker context path, which is set to the project root directory by default. If you want the `Dockerfile` to use its containing directory as the context root, use `fly deploy `. > * When specifying a local Dockerfile, make sure it's not excluded from the Docker build context in your `.dockerignore`. ### Specify a Docker ignore file ```toml theme={null} [build] ignorefile = "/path/.dockerignore" ``` `ignorefile` accepts a relative path to a `.dockerignore` file. By default, `flyctl` looks for the `.dockerignore` file in the working directory. ### Specify a multistage Docker build target ```toml theme={null} [build] build-target = "test" ``` If your Dockerfile has [multiple stages](https://docs.docker.com/develop/develop-images/multistage-build/), you can specify one as the target for deployment. The target stage must have a `CMD` or `ENTRYPOINT` set. ### Specify Docker build arguments You can pass [build-time arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg) to both Dockerfile and Buildpack builds using the `[build.args]` sub-section: ```toml theme={null} [build.args] USER="plugh" MODE="production" ``` You can also pass build arguments to `flyctl deploy` using `--build-arg`. Command line args take priority over args with the same name in `fly.toml`. Note that build arguments are *not available* in the runtime container. If you need build information at runtime - like a Git revision - store it in a file at build time, like: ``` RUN echo $GIT_REVISION > REVISION ``` Likewise, application environment variables and secrets are not available to builds. ## The `deploy` section This section configures deployment-related settings such as the release command or deployment strategy. ### Run one-off commands before releasing a deployment 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 theme={null} [deploy] release_command = "bin/rails db:prepare" ``` 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 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]`, for example: ```toml theme={null} [deploy] release_command = "bin/rails db:prepare" [deploy.release_command_vm] size = "performance-1x" memory = "8gb" ``` A non-zero exit status from this command will stop the deployment. `fly deploy` will display logs from the command. Logs are available via `fly logs` as well. To ensure the command runs in a specific region, such as `dfw`, set `PRIMARY_REGION = 'dfw'` in your application environment in `fly.toml` or with `fly deploy -e PRIMARY_REGION=dfw`. Setting `PRIMARY_REGION` is important if you're running [database replicas in multiple regions](/unmanaged-postgres/advanced-guides/high-availability-and-global-replication). The environment variable `RELEASE_COMMAND=1` is set within the temporary release Machine. You can use this to define behavior in your Dockerfile's `ENTRYPOINT` that's conditional on being run on a release Machine. 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](/blueprints/seamless-deployments#one-off-tasks-with-release_command). > [!NOTE] > > **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 theme={null} [deploy] strategy = "bluegreen" ``` `strategy` controls the way a new release replaces the previous release. Different strategies offer trade-offs between downtime and reliability. `strategy` may also be specified at deploy time with `flyctl deploy --strategy`. The available strategies are: **rolling**: The default strategy for apps with or without volumes. One by one, each running Machine is taken down and replaced by a new release Machine. **immediate**: Replace all Machines with new releases immediately without waiting for health checks to pass. This is useful in emergency situations where you're confident about release health and can't wait for health checks. **canary**: Boots a single, new Machine with the new release, verifies its health, and then proceeds with a `rolling` restart strategy. The `canary` strategy cannot be used for Machines with attached volumes. **bluegreen**: For every running Machine, a new one is booted alongside it in the same region. Once all of the new Machines pass health checks, traffic gets migrated to the new Machines and the old Machines are destroyed. If your app is scaled to 2 or more Machines, this strategy can reduce deploy time by running tasks in parallel. You need to configure at least one health check to use the `bluegreen` strategy. The `bluegreen` strategy cannot be used for Machines with attached volumes. > [!NOTE] > > **Note:** If `max-per-region` is set to 1, then the default strategy is set to `rolling`. This happens because `canary` needs to temporarily run more than one Machine to work correctly. The `bluegreen` strategy will behave similarly with `max-per-region` set to 1. #### Rolling deploy configuration ```toml theme={null} [deploy] strategy = "rolling" max_unavailable = 1 ``` For rolling deploys, you can use `max_unavailable` to control how many Machines can be down at a time. Whole number values, greater than or equal to one, specify exactly how many Machines can be down at a time. For example, a value of "1" would update one-at-a-time. Fractional values, between zero and one, provide a percentage of Machines that can be down at a time. For example, the default value of 0.33 means that a maximum of one third of your Machines can be down during a deploy. ### 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. 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: ```toml theme={null} [deploy] wait_timeout = "10m" ``` ## The `env` variables section ```toml theme={null} [env] LOG_LEVEL = "debug" RAILS_ENV = "production" S3_BUCKET = "my-app-production" ``` This optional section allows setting non-sensitive information as environment variables in the application's [runtime environment](/machines/runtime-environment). For sensitive information, such as credentials or passwords, use the [secrets CLI command](/apps/secrets). Env variable names are strictly case-sensitive and cannot begin with `FLY_` (as this could clash with the [runtime environment](/machines/runtime-environment)). Env values can only be strings. Secrets take precedence over env variables with the same name. **Note:** In Apps V2, the [`primary_region` option](#primary-region) sets the `PRIMARY_REGION` environment variable within a Machine and overrides any value set in the `[env]` section. ## The `http_service` section For apps that only need HTTP and HTTPS services, you can replace the `[[services]]` section with this simpler alternative. An `[http_service]` section defines a service that listens on ports 80 and 443. Port 80 will have an HTTP handler. Port 443 will have a TLS and HTTP handler. You can configure additional services on different ports by adding `[[services]]` sections. As with the more verbose [`[[services]]`](#the-services-sections), you can specify a list of [processes](#the-processes-section) to limit the service definition to Machines belonging to process groups that should receive HTTP requests. ```toml theme={null} [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 ``` * `processes`: For apps with multiple processes. The process group that this service belongs to. Define process groups in the [processes](#the-processes-section) section. * `internal_port`: The port this service (and application) will use to communicate with clients. The default is 8080. We recommend applications use the default. * `force_https`: A Boolean which determines whether to enforce HTTP to HTTPS redirects. * `auto_stop_machines`: The action, if any, that Fly Proxy should take when the app is idle for several minutes. Options are `"off"`, `"stop"`, or `"suspend"`. If `"off"`, Fly Proxy will never stop Machines. If `"stop"`, Fly Proxy stops Machines when the app has excess capacity. If `"suspend"`, Fly Proxy suspends Machines (if possible) when the app has excess capacity. Starting a Machine from a `suspended` state is faster than starting a Machine from a `stopped` state, but there are [some caveats](https://community.fly.io/t/autosuspend-is-here-machine-suspension-is-enabled-everywhere/20942). If there's only one Machine in a region, then the Machine is stopped or suspended if it has no traffic. The Fly Proxy runs a process to automatically stop Machines every few minutes. The default if not set is `"off"`. * `auto_start_machines`: Whether to automatically start an application's Machines when a new request is made to the application and there's no excess capacity, per region. If there's only one Machine in a region, then it's started whenever a request is made to the application. The default if not set is `true`. * `min_machines_running`: The number of Machines to keep running, in the primary region only, when `auto_stop_machines` is `"stop"` or `"suspend"`. The default if not set is `0`. > [!NOTE] > > We recommend configuring `auto_stop_machines` and `auto_start_machines` so that they are both enabled or both disabled, to avoid having Machines that either never start or never stop. Learn more about [how Fly Proxy autostop/autostart works](/reference/fly-proxy-autostop-autostart), including how Fly Proxy determines excess capacity in a region using the `soft_limit` setting. ### `http_service.concurrency` The `http_service` concurrency section configures how to measure load for an application to inform Fly Proxy [load balancing](/reference/load-balancing) and [autostop/autostart](/launch/autostop-autostart). The `[http_service.concurrency]` section has the same settings as `[services.concurrency]`, which are: * `type` specifies what metric is used to determine when to auto start or stop, or when a given Machine should receive more or less traffic (load balancing). The two supported values are `connections` and `requests`. **connections**: Load balance and scale based on the number of concurrent TCP connections. This is the default when unspecified. This is also the default when `fly.toml` is created with `fly launch`. A new connection is established for each HTTP request; consider the `requests` type for HTTP apps. **requests**: Load balance and scale based on the number of HTTP requests. This is recommended for web services, because the Fly Proxy can pool and reuse connections for requests. * `hard_limit` : When a Fly Machine is at or over this number of concurrent connections or requests, the system will stop sending new traffic to that Machine. If unset, no `hard_limit` is enforced. * `soft_limit` : When a Fly Machine is at or over this number of concurrent connections or requests, the system will deprioritize sending new traffic to that Machine and only send it to that Machine if all other Machines are also at or above their `soft_limit`. This setting is also used to determine excess capacity for the [autostop/autostart feature](/launch/autostop-autostart). If unset, the default is 20. For more information about concurrency settings, see [Guidelines for concurrency settings](/apps/concurrency). ### `http_service.http_options.idle_timeout` Configure an idle-timeout for connections to your app. ```toml theme={null} [http_service.http_options] idle_timeout = 600 ``` ### `http_service.http_options.response.pristine` Configures Fly Proxy to not add any Fly headers to HTTP responses. The following response headers won't be added and won't be modified if returned by the app: * `Server` * `Via` * `Fly-Request-Id` * `Fly-Cache-Status` This is useful if you are building a platform on top of Fly and have your own load balancer that already adds similar response headers. ```toml theme={null} [http_service.http_options.response] pristine = true ``` ### `http_service.http_options.response.headers` Add or remove HTTP response headers. In the following example, `Example-Header` will be removed from the final response that Fly Proxy sends, while `Example-Header-1` and `Example-Multi-Value` and their values will be added to the response header. ```toml theme={null} [http_service.http_options.response.headers] Example-Header = false Example-Header-1 = "value" Example-Multi-Value = ["value1", "value2"] ``` ### `http_service.http_options.h2_backend` Indicates whether the app supports HTTP/2 cleartext (H2C) with prior knowledge or not. In the following example, `h2_backend` is set to `true`, which allows the app to expose HTTP/2-only service, like gRPC, via `http` handler. ```toml theme={null} [http_service.http_options] 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 theme={null} [[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](/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: ```toml theme={null} [http_service.tls_options] alpn = ["h2", "http/1.1"] versions = ["TLSv1.2", "TLSv1.3"] default_self_signed = false ``` * `alpn`: Array of strings indicating how to handle ALPN negotiations with clients. * `versions`: Array of strings indicating which TLS versions are allowed. * `default_self_signed`: When `true`, serve a self-signed certificate if no certificate exists. Default is `false`. Fly.io can also terminate TLS only and pass through directly to your service. For more information, refer to [services.ports.tls\_options](/reference/configuration#services-ports-tls_options) ### `http_service.checks` To configure health checks for your HTTP service, you can use the `http_service.checks` section. These checks expect a successful HTTP status in response (i.e, 2xx). Here is an example of a `http_service.checks` section: ```toml theme={null} [[http_service.checks]] grace_period = "10s" interval = "30s" method = "GET" timeout = "5s" path = "/" ``` Roughly translated, this section says every thirty seconds, perform an HTTP GET on the root path of the service, looking for it to return an HTTP 200 status within five seconds. The check runs against the service on the Machine over the app's private network, not against the public `appname.fly.dev` hostname, so it works even for services that aren't exposed publicly. The parameters of a `check` are listed below. 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 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. * `protocol`: The protocol to be used (`http` or `https`). If omitted, the default is `http`. * `tls_server_name`: If the protocol is `https`, the hostname to use for TLS certificate validation. * `tls_skip_verify`: When `true` (and using HTTPS protocol) skip verifying the certificates sent by the server. * `http_service.checks.headers`: This is a sub-section of `http_service.checks`. It uses the key/value pairs as a specification of header and header values that will get passed with the check call. > [!WARNING] > > The health check will not automatically follow any `HTTP 301` or `302` redirect, so it will fail if it receives anything other than a `200 OK` response. This may be an issue if your heath check specifies `protocol = http` (the default) but your app forces `https`. You may be able to avoid this redirect and continue using `http` for your health check by adding `X-Forwarded-Proto = "https"` to the `[http_service.checks.headers]` sub-section. ### 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. ```toml theme={null} [[http_service.machine_checks]] image = "curlimages/curl" entrypoint = ["/bin/sh", "-c"] command = ["curl http://[$FLY_TEST_MACHINE_IP] | grep 'Hello, World!'"] kill_signal = "SIGKILL" kill_timeout = "5s" ``` ## The `services` sections The `services` sections define how Fly Proxy connects requests that hit an app's public [Anycast](/networking/services) or private [Flycast](/networking/flycast) address to services running within Machines, and configure other Fly Proxy behavior for a service. If a service only needs HTTP and HTTPS on standard ports, you can use the less verbose [`http_service`](#the-http_service-section). A `services` section itself is a table of tables in TOML, so it is delimited with double square brackets. Tables within `[[services]]` configure additional features and behaviors for the service, and are described in subsequent sections. > [!WARNING] > > At least one [`services.ports`](#services-ports) entry is required for each `services` section, to set a port and handler for incoming requests. An app can have: * No `services` section (and no [`http_service` section](#the-http_service-section)): The application cannot be reached from the public internet, nor by a [Flycast](/networking/flycast) address; this is typical of apps that talk only over [6PN private networking](/networking/private-networking) to other apps on the same network. * One `services` section (or an [`http_service` section](#the-http_service-section)): One internal port mapped to one or more external ports. * Multiple `services` sections (or an [`http_service` section](#the-http_service-section) and one or more `services` sections): Mapping multiple internal ports to multiple external ports. Example (note the double square brackets): ```toml theme={null} [[services]] internal_port = 8080 protocol = "tcp" auto_stop_machines = "stop" auto_start_machines = true min_machines_running = 0 ``` Settings: * `processes`: For apps with multiple process groups, the process group or groups that this service belongs to. Define process groups in the [processes](#the-processes-section) section. * `internal_port` : The port this service (and application) will use to communicate with clients. The default is 8080. We recommend applications use the default. * `protocol` : The protocol that this service will use to communicate. Typically `tcp` for most applications, but can also be `udp`. * `auto_stop_machines`: The action, if any, that Fly Proxy should take when the app is idle for several minutes. Options are `"off"`, `"stop"`, or `"suspend"`. If `"off"`, Fly Proxy will never stop Machines. If `"stop"`, Fly Proxy stops Machines when the app has excess capacity. If `"suspend"`, Fly Proxy suspends Machines (if possible) when the app has excess capacity. Starting a Machine from a `suspended` state is faster than starting a Machine from a `stopped` state, but there are [some caveats](https://community.fly.io/t/autosuspend-is-here-machine-suspension-is-enabled-everywhere/20942). If there's only one Machine in a region, then the Machine is stopped or suspended if it has no traffic. The Fly Proxy runs a process to automatically stop Machines every few minutes. The default if not set is `"off"`. * `auto_start_machines`: Whether to automatically start an application's Machines when a new request is made to the application and there's no excess capacity, per region. If there's only one Machine in a region, then it's started whenever a request is made to the application. The default if not set is `true`. * `min_machines_running`: The number of Machines to keep running, in the primary region only, when `auto_stop_machines` is `"stop"` or `"suspend"`. The default if not set is `0`. > [!NOTE] > > We recommend configuring `auto_stop_machines` and `auto_start_machines` so that they are both enabled or both disabled, to avoid having Machines that either never start or never stop. Learn more about [how Fly Proxy autostop/autostart works](/reference/fly-proxy-autostop-autostart), including how Fly Proxy determines excess capacity in a region using the `soft_limit` setting. ### `services.ports` For each `services` section in your `fly.toml`, i.e. for each external port you want to accept connections on, you need a `services.ports` section. The section is denoted by double square brackets like this: ```toml theme={null} [[services.ports]] handlers = ["http"] port = 80 force_https = true # optional ``` This example defines an HTTP handler on port 80. * `handlers` : An array of strings, each string selecting a handler process to terminate the connection with at the edge. Here, the `http` handler will accept HTTP traffic and pass it on to the `internal_port` of the application, which we defined in the `services` section above. For the list of available handlers, and how they manage network traffic, see the [Public Network Services documentation](/networking/services). * `port` : An integer representing the external port to listen on (ports 1-65535). * `start_port` : For a port range, the first port to listen on. * `end_port` : For a port range, the last port to listen on. * `force_https`: A Boolean which determines whether to enforce HTTP to HTTPS redirects. You can have more than one `services.ports` section. The default configuration, for example, contains two. We've already seen one above. The second one defines an external port 443 for secure connections, using the `tls` handler. ```toml theme={null} [[services.ports]] handlers = ["tls", "http"] port = 443 ``` For UDP applications, make sure to bind the application to the same port as defined in the relevant `services.ports` section. For example, the configuration below will listen on port 5000 and the application will need to bind to `fly-global-services:5000` to receive traffic. Leave `handlers` unset for UDP services. ```toml theme={null} [[services]] internal_port = 5000 protocol = "udp" [[services.ports]] port = 5000 ``` Instead of using multiple port definitions, you can specify a range of ports. For example, the configuration below will listen on the ports `8080`, `8081`, `8082`, `8083`, `8084` and `8085`: ```toml theme={null} [[services.ports]] handlers = ["tls"] start_port = 8080 end_port = 8085 ``` ### `services.ports.http_options.idle_timeout` Configure an idle-timeout for connections to your app. ```toml theme={null} [services.ports.http_options] idle_timeout = 600 ``` ### `services.ports.http_options.response.pristine` Configures Fly Proxy to not add any Fly headers to HTTP responses. The following response headers won't be added and won't be modified if returned by the app: * `Server` * `Via` * `Fly-Request-Id` * `Fly-Cache-Status` This is useful if you are building a platform on top of Fly and have your own load balancer that already adds similar response headers. ```toml theme={null} [services.ports.http_options.response] pristine = true ``` #### `services.ports.http_options.response.headers` Add or remove HTTP response headers. In the following example, `Example-Header` will be removed from the final response that Fly Proxy sends, while `Example-Header-1` and `Example-Multi-Value` and their values will be added to the response header. ```toml theme={null} [services.ports.http_options.response.headers] Example-Header = false Example-Header-1 = "value" Example-Multi-Value = ["value1", "value2"] ``` ### `services.ports.http_options.h2_backend` Indicates whether the app supports HTTP/2 cleartext (H2C) with prior knowledge or not. In the following example, `h2_backend` is set to `true`, which allows the app to expose HTTP/2-only service, like gRPC, via `http` handler. ```toml theme={null} [services.ports.http_options] 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 theme={null} [[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](/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. For example: ```toml theme={null} [[services.ports]] handlers = ["proxy_proto"] port = 5000 proxy_proto_options = { version = "v2" } ``` * `version` : A string to indicate that the TCP connection uses PROXY protocol version 2. The default when not set is version 1. #### `services.ports.tls_options` Configure the TLS versions and ALPN protocols that Fly's edge will use to terminate TLS for your application with: ```toml theme={null} [[services.ports]] handlers = ["tls", "http"] port = 443 tls_options = { "alpn" = ["h2", "http/1.1"], "versions" = ["TLSv1.2", "TLSv1.3"] } ``` * `alpn` : Array of strings indicating how to handle ALPN negotiations with clients. * `versions` : Array of strings indicating which TLS versions are allowed. * `default_self_signed`: When `true`, serve a self-signed certificate if no certificate exists. Default is `false`. Fly.io can also terminate TLS only and pass through directly to your service. This works for a variety of applications that can benefit from offloading TLS termination and accept the unencrypted connection. One use case is transmitting messages over MQTT. Fly's edge terminates TLS and sends unencrypted MQTT directly to your application through our backhaul. The config below will negotiate MQTT over TLS with clients, and then send unencrypted MQTT to the application: ```toml theme={null} [[services.ports]] handlers = ["tls"] port = 443 tls_options = { "alpn" = ["mqtt"] } ``` ### `services.concurrency` The services concurrency sub-section configures how to measure load for an application to inform Fly Proxy [load balancing](/reference/load-balancing) and [autostop/autostart](/launch/autostop-autostart). This section is a simple list of key/values, so the section is denoted with single square brackets like so: ```toml theme={null} [services.concurrency] type = "connections" hard_limit = 25 soft_limit = 20 ``` `type` specifies what metric is used to determine when a given Machine has reached a concurrency limit. The two supported values are `connections` and `requests`. **connections**: Load balance and scale based on the number of concurrent TCP connections. This is the default when unspecified. This is also the default when `fly.toml` is created with `fly launch`. A new connection is established for each HTTP request; consider the `requests` type for HTTP apps. **requests**: Load balance and scale based on the number of HTTP requests. This is recommended for web services, because the Fly Proxy can pool and reuse connections for requests. * `hard_limit` : When a Fly Machine is at or over this number of concurrent connections or requests, the system will stop sending new traffic to that Machine. If unset, no `hard_limit` is enforced. * `soft_limit` : When a Fly Machine is at or over this number of concurrent connections or requests, the system will deprioritize sending new traffic to that Machine and only send it to that Machine if all other Machines are also at or above their `soft_limit`. This setting is also used to determine excess capacity for the [autostop/autostart feature](/launch/autostop-autostart). If unset, the default is 20. For more information about concurrency settings, see [Guidelines for concurrency settings](/apps/concurrency). ### `services.tcp_checks` When a service is running, Fly Proxy can check up on it by connecting to a port. The `services.tcp_checks` section defines parameters for those checks. For example, the default `tcp_checks` looks like this: ```toml theme={null} [[services.tcp_checks]] grace_period = "1s" interval = "15s" timeout = "2s" ``` 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 application to start up. For example if your app takes 2 seconds to start up, give it some runway by setting this to at least 3 seconds. * `interval`: The time between connectivity checks. If the interval is long, and the `grace_period` is shorter than your app's startup time, then the health check will take longer and will add to your deployment time. * `timeout`: The maximum time a connection can take before being reported as failing its health check. ### `services.http_checks` Another way of checking a service is running is through HTTP checks as defined in the `services.http_checks` section. These checks are more thorough than `services.tcp_checks` as they require not just a connection but a successful HTTP status in response (i.e, 2xx). Here is an example of a `services.http_checks` section: ```toml theme={null} [[services.http_checks]] interval = 10000 grace_period = "5s" method = "get" path = "/" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] ``` Roughly translated, this section says every ten seconds, perform an HTTP GET on the root path of the service, looking for it to return an HTTP 200 status within two seconds. The check runs against the service on the Machine over the app's private network, not against the public `appname.fly.dev` hostname, so it works even for services that aren't exposed publicly. The parameters of a `http_check` are listed below. 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. * `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. * `path`: The path of the URL to be requested. * `protocol`: The protocol to be used (`http` or `https`). * `tls_server_name`: If the protocol is `https`, the hostname to use for TLS certificate validation. * `tls_skip_verify`: When `true` (and using HTTPS protocol) skip verifying the certificates sent by the server. * `services.http_checks.headers`: This is a sub-section of `services.http_checks`. It uses the key/value pairs as a specification of header and header values that will get passed with the http\_check call. **Note**: The `services.http_checks` section is optional and not generated in the default `fly.toml` file. ### `services.machine_checks` Machine checks work a bit differently than the other checks. They run on each deploy, and if they fail, the deploy is stopped. A new Machine is spawned with the environment variable `FLY_TEST_MACHINE_IP` set to the [6PN IPv6 address](/networking/private-networking#6pn-addresses-in-detail) of the Machine being tested. This is useful for running integration tests on Machines before a deployment. Here's an example: ```toml theme={null} [[services.machine_checks]] image = "curlimages/curl" entrypoint = ["/bin/sh", "-c"] command = ["curl http://[$FLY_TEST_MACHINE_IP] | grep 'Hello, World!'"] kill_signal = "SIGKILL" kill_timeout = "5s" ``` This example uses the `curl` image to run a test. It checks that the Machine is serving a page with the text "Hello, World!". * `image`: The Docker image to use for the test. We default to the one used for the process group the Machine is in. * `entrypoint`: The entrypoint for the test. Defaults to the entrypoint of the Machine being tested if not set * `command`: The command to run for the test. * `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 supported for apps using the `rolling` and `canary` deployment strategies. ## The `mounts` section This section supports the Volumes feature for persistent storage. The section has two required entries: `source` and `destination`. ```toml theme={null} [mounts] source = "myapp_data" destination = "/data" processes = ["disk"] # optional - attach volumes to Machines that belong to one or more process groups initial_size = "20gb" # optional - the size of the volume created on first deploy ``` ### `source` The `source` is a volume name that this app should mount. Any volume with this name, in the same region as the app and that isn't already mounted, may be mounted. A volume of this name must exist in *some* region for the application to deploy. ### `destination` The `destination` is the directory where the `source` volume should be mounted on the running app. This cannot be `/` since that's where the root file system is mounted. ### `processes` Optionally, you can specify a list of [processes](#the-processes-section) to limit volume mounts by process group. You can even specify two different `[[mounts]]` sections to mount different source volumes to Machines in different process groups. Note the need to use double brackets if there are multiple `[[mounts]]` defined. ### `initial_size` Optionally, you can specify the size of the volume to be created when launching an app for the first time. This value is only used by `fly launch` and `fly deploy` commands in case they need to create a new volume; it is ignored by the `fly volume create` command. The value can be a string with units, like `initial_size = "30GB"` for example. The value can also be an integer, like `initial_size = 30` for example, with GB as the default unit. ### `snapshot_retention` Optionally, you can specify the number of days snapshots of the volumes in this app are retained. The default is 5 days when unset. The minimum is 1 day. The maximum is 60 days. For example, the following config retains snapshots of volumes for 14 days: ```toml theme={null} [mounts] source = "myapp_data" destination = "/data" 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 theme={null} [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`, 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`. ### `auto_extend_size_limit` Maximum size, in GB, of a volume after an extension is completed. > [!NOTE] > > 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. ```toml theme={null} [[mounts]] source = "data" destination = "/data" auto_extend_size_threshold = 80 auto_extend_size_increment = "1GB" auto_extend_size_limit = "5GB" ``` ## The `vm` section This section defines the compute requirements for the Machines used for the application. The default Machine size is `shared-cpu-1x` but it is not enforced if not specified in `fly.toml`. It means commands like `fly deploy` won't attempt to update the compute requirements (or size) for a Machine. `fly scale` commands will use existing Machines to infer new Machine sizes, and in the event that there are no Machines, they will use `shared-cpu-1x`. 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](/launch/scale-machine#machine-size-configuration-precedence). > [!NOTE] > > 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 theme={null} [[vm]] size = "shared-cpu-2x" memory = "1gb" cpus = 2 cpu_kind = "shared" kernel_args = "no-hlt=true" host_dedication_id = "customer-id" persist_rootfs = "never" processes = ["app"] ``` ### `size` The Machine preset size to use as base, like `size = "shared-cpu-1x"` for example. To get the full list, run `fly platform vm-sizes` or check the [pricing page](/about/pricing#compute). ### `memory` The memory to request. It can be expressed with units, like `memory = "2GB"` or `memory = "512mb"` for example. The value can also be an integer, like `memory = 1024` for example, with MB as the default unit. ### `cpus` The number of vCPUs to request. Valid values are `1`, `2`, `4`, `8`, or `16`, but depends on `cpu_kind`. ### `cpu_kind` The kind of CPU to request. Valid values are `shared` and `performance`. ### `kernel_args` Additional kernel parameters provided to the kernel when booting the VM. ### `host_dedication_id` 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. A `[[vm]]` section without a `processes` key applies to all process groups, except when another `[[vm]]` section targets a specific process group. For example the following snippets uses `shared-cpu-4x` for `app` and `other` process groups but `performance-1x` for `worker` group. ``` [processes] app = "/start-app" worker = "/start-worker" other = "/start-other" [[vm]] size = "shared-cpu-4x" [[vm]] size = "performance-1x" processes = ["worker"] ``` ## The `restart` section ```toml theme={null} [[restart]] policy = "never" retries = 10 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: * 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. * on-failure: we’ll only restart the machine if it exited with a non-zero exit code (due to a failure or crash). This is the default policy if one is not explicitly set. A restart policy can be targeted to a specific process group. If a group is not specified, all machines in an app will have the same default restart policy. You can also specify the number of times we should retry restarting the machine before giving up. ## The `checks` section If your app doesn't have public-facing services, or you want independent health checks that don't affect request routing, use this top-level `checks` section instead of `[[services.checks]]`. Unlike service-level checks, top-level checks require: * A port number * A unique name * The type of check (`http` or `tcp`) ```toml theme={null} [checks] [checks.name_of_your_http_check] grace_period = "30s" interval = "15s" method = "get" path = "/path/to/status" port = 5500 timeout = "10s" type = "http" [checks.name_of_your_http_check.headers] Content-Type = "application/json" Authorization = "super-secret" [checks.name_of_your_tcp_check] grace_period = "30s" interval = "15s" port = 1234 timeout = "10s" type = "tcp" ``` Fields are very similar to `[[services.checks]]`: * `port`: Internal port to connect to. Needs to be available on `0.0.0.0`. Required. * `type`: Either `tcp` or `http`. Required. * `grace_period`: The time to wait after a Machine starts before checking its health. Make sure you give your app enough time to start up. For example if your app takes 2 seconds to start up, give it some runway by setting this to at least 3 seconds. * `interval`: The time between check runs. If your `grace_period` is shorter than your app's startup time, and `interval` is too long, checks will increase deployment times. * `processes`: For apps with multiple processes. The process group to apply the health checks to. Define process groups in the [processes](#the-processes-section) section. For HTTP checks only: * `method`: The HTTP method to be used for the check. * `path`: The path of the URL to be requested. * `timeout`: The maximum time a connection can take before being reported as failing its health check. * `headers`: Specify key/value pairs will get passed as HTTP headers on the check request. Again, times are in milliseconds unless units are specified. ## The `processes` section The `processes` section allows you to define process groups to be run on separate Machines within a single app. Learn more about [running multiple process groups in an app](/launch/processes). **Each Machine can run a different command on start**, allowing you to re-use your code base for different tasks (web server, queue worker, etc). To run multiple processes, you'll need a `[processes]` block containing a map of a name and command to start the application. ```toml theme={null} [processes] web = "bundle exec rails server -b [::] -p 8080" worker = "bundle exec sidekiqswarm" ``` Furthermore, you can "match" a specific process (or processes) to an [`[http_service]`](#the-http_service-section) or [`[[services]]`](#the-services-sections) configuration. For example: ```toml theme={null} [http_service] processes = ["web"] # this service only applies to the web process internal_port = 8080 force_https = true ``` Volumes can also be assigned to specific processes; use double brackets to include more than one [`[[mounts]]`](#the-mounts-section) section, and mount differently named volumes to Machines in different process groups: ```toml theme={null} [[mounts]] source = "data" destination = "/data" processes = ["app"] ``` ## The `metrics` section Fly apps can be configured to export custom metrics to the Fly.io-hosted Prometheus service. ```toml theme={null} [metrics] port = 9091 path = "/metrics" # default for most prometheus exporters ``` Custom metrics can be also configured for specific `processes`, using double brackets to define multiple `[[metrics]]` tables: ```toml theme={null} [[metrics]] port = 9394 path = "/metrics" processes = ["web"] [[metrics]] port = 9113 path = "/metrics" processes = ["proxy"] ``` Check out [Metrics on Fly.io](/monitoring/metrics) for more information about collecting metrics for your apps. ## The `statics` sections 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 theme={null} [[statics]] guest_path = "/app/public" url_prefix = "/public" ``` 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. 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 theme={null} [[statics]] guest_path = "/app/public" url_prefix = "/public" tigris_bucket = "my-bucket" ``` 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 theme={null} [[statics]] guest_path = "/app/public" url_prefix = "/public" tigris_bucket = "my-bucket" index_document = "index.html" ``` 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`. Tigris statics are billed at Tigris pricing. Data egress from Tigris may incur both Tigris and Fly Proxy egress charges. Do not overlap `url_prefix` values; only one static mapping will be honored for a given prefix. ### Caveats This feature should not be compared directly with a CDN, for the following reasons: * 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. ## The `files` section The `files` section lets you place files inside your Machine at deploy time. This is useful for configuration files, credentials, or other data your app needs at a known path. Each `[[files]]` entry requires a `guest_path` and exactly one source (`raw_value`, `local_path`, or `secret_name`): * `guest_path`: The absolute path where the file will be created inside the Machine (e.g., `/app/config.yaml`). This should match wherever your app expects to find the file. To find your app's working directory, check the `WORKDIR` in your Dockerfile. * `raw_value`: Inline file content, base64 encoded. You can encode a value with `base64 < file.txt` on macOS/Linux, or `[Convert]::ToBase64String([IO.File]::ReadAllBytes("file.txt"))` in PowerShell. * `local_path`: Path to a file on your local machine (where you run `fly deploy`), relative to the `fly.toml` directory or as an absolute path. The file content does not need to be base64 encoded. * `secret_name`: The name of an app secret set with [`fly secrets set`](/flyctl/cmd/fly_secrets_set). The secret value must be base64 encoded before being set (e.g., `fly secrets set SUPER_SECRET=$(base64 < secret.txt)`). ### Examples Place a config file from your project into the Machine: ```toml theme={null} [[files]] guest_path = "/app/config.yaml" local_path = "config/production.yaml" ``` For example, if your project contains `config/production.yaml`, this will copy its content to `/app/config.yaml` inside the Machine on each deploy. Inline a small file using base64-encoded content: ```toml theme={null} [[files]] guest_path = "/app/hello.txt" raw_value = "aGVsbG8gd29ybGQK" ``` The value `aGVsbG8gd29ybGQK` is the base64 encoding of `hello world\n`. This creates `/app/hello.txt` containing `hello world`. Write a secret to a file: ```toml theme={null} [[files]] guest_path = "/app/credentials.json" secret_name = "DB_CREDENTIALS" ``` This writes the (base64-decoded) content of the `DB_CREDENTIALS` secret to `/app/credentials.json`. Set the secret with: `fly secrets set DB_CREDENTIALS=$(base64 < credentials.json)`. You can optionally restrict which Machine(s) contain the file using the [processes](#the-processes-section) field. For example: ```toml theme={null} [[files]] guest_path = "/path/to/config.yaml" local_path = "/local/path/config.yaml" processes = ["web"] ``` To remove a previously configured file on the next deployment, either delete the `files` entry entirely, or set its source to an empty string like this: ```toml theme={null} [[files]] guest_path = "/path/to/config.yaml" local_path = "" ``` ## The `experimental` section This section is for flags and feature settings which have yet to be promoted into the main configuration. ```toml theme={null} [experimental] cmd = ["path/to/command", "arg1", "arg2"] entrypoint = ["path/to/command", "arg1", "arg2"] exec = ["path/to/command", "arg1", "arg2"] ``` ### `cmd` This overrides the `CMD` set by the Dockerfile. It should be specified as an array of strings, as seen in the example above. ### `entrypoint` This overrides the `ENTRYPOINT` set by the Dockerfile. It should be specified as an array of strings, as seen in the example above. ### `exec` This overrides both the `CMD` and the `ENTRYPOINT` set by the Dockerfile. It should be specified as an array of strings, as seen in the example above. # Content Encoding with the Fly Proxy Source: https://docs.fly.io/reference/content-encoding HTTP responses get compressed at the Fly Proxy edge with zstd, Brotli, gzip, or deflate per Accept-Encoding. Set Content-Encoding: none to stream HTML. > [!NOTE] > > 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. 2. **Brotli** — Still excellent, especially for text. Smaller files than gzip in many cases. 3. **gzip** — The fallback. Widely supported. 4. **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](/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](/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 theme={null} 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](/reference/configuration) * [How Fly.io handles HTTP](/networking/services) * [Tips to fine-tune your app](/apps/fine-tune-apps) # Provisioning Fly.io Extensions Source: https://docs.fly.io/reference/extensions_api Spec for Fly.io extension partners: signed provisioning requests, Flycast private access, OAuth SSO, resource webhooks, and billing. Read our [Extensions Program overview](/about/extensions) before digging into this doc. ## Requirements To implement the following, we need to provide you with: * A shared secret for [signing provisioning requests](/reference/extensions_api#authentication-and-request-signing) * A shared secret for [signing webhooks](/reference/extensions_api#webhook-authentication-and-request-signing) * OAuth application client and secret values You'll need to give us an OAuth redirect URL for your production environment. ## Provisioning Flow Fly.io is a CLI-first platform. So provisioning extensions starts there. Either when launching an app, or when a customer explicitly asks to provision an extension. We'll forward this request on to you, along with details about the provisioning Fly.io organization. You should reply synchronously with details about your provisioned extension. We'll attach these details to a target application, where applicable, as secrets exposed as environment variables in VMs. Note: If resources take more than 5 seconds to provision, `flyctl` can poll your API for resource readiness. ## Provisioning Accounts and Organizations We'd like customers to have full access to your platform features through their Fly.io-provisioned account. And you should be able to communicate with customers via email. To that end, all provisioning requests are accompanied by: * A unique user ID and working email alias * A unique organization ID and working email alias that emails org admins Our SSO flow allows you to fetch user and organization data, to determine whether the user has access to a given resource. You should provision, at least: * An account to own extensions provisioned through Fly.io * An organization/team, where applicable, to match the Fly.io organization * One or more administrative users as account owners and SSO targets We recommend provisioning organizations and users idempotently along with the resource provisioning request, or along with SSO sign-in, as described below. This avoid having to implement a separate account provisioning API. ## Resource Provisioning API This section covers describes a REST-based API for provisioning individual extension resources. ### Authentication and Request Signing Given the high privilege afforded to us by your platform, we sign all requests with a shared secret and a SHA256 HMAC. We require that providers verify these signatures. All requests are accompanied by an `X-Signature` header containing the HMAC of the request contents: the query string for `GET` and `DELETE` requests, and the request body for `POST` and `PATCH` requests. We add the following parameters to help you verify the authenticity of requests. We recommend at least verifying that the signed URL matches the request URL. ``` timestamp: A UNIX epoch timestamp value nonce: A random unique string identifying the individual request url: The full request target URL ``` ### Request Base URL We recommend that your partner API builds upon base URL, like `https://logjam.io/flyio`. You can append then the paths shown below as REST resources. ### Resource provisioning Here's an example provisioning request. ```javascript theme={null} POST https://logjam.io/flyio/extensions { name: "test", id: "test", organization_id: "04La2mblTaz", organization_name: "High Flyers", user_email: "v9WvKokd@customer.fly.io", user_id: "NeBO2G0l0yJ6", user_role: "admin", primary_region: "mad", ip_address: "fdaa:0:47fb:0:1::1d", read_regions: ["syd", "scl"], url: "https://logjam.io/flyio/extensions", nonce: "e3f7b4c8f5ea016d0d2e92048ca0d856", timestamp: 1685641159 } ``` **Parameters** These parameters are sent with every provisioning request. | Name | Type | Description | Example | | - | - | - | - | | **name** | string | Name of the extension, unique across the Fly.io platform | `collider-prod` | | **id** | string | Unique ID representing the extension | `30bqn49y2jh` | | **organization\_id** | string | Unique ID representing an organization | `M03FclA4m` | | **organization\_name** | string | Display name for an organization | `Supercollider Inc` | | **user\_id** | string | Unique ID representing the provisioning user | `M03FclA4m` | | **user\_email** | string | Obfuscated email that routes to the **provisioning** user (does not change) | `n1l330mao@customer.fly.io` | | **user\_role** | string | Provisioning user's role | `admin, member` | **Optional parameters** These parameters would only be sent for extensions that are region-aware or that deploy on Fly.io. These parameters are sent with every provisioning request. | Name | Type | Description | Example | | - | - | - | - | | **primary\_region** | string | The three-letter, primary Fly.io region where the target app intends to write from | `mad` | | **read\_regions** | array | An array of Fly.io region codes where read replicas should be provisioned | `["mad", "ord"]` | | **ip\_address** | string | An IPv6 address on the customer network assigned to this extension | `fdaa:0:47fb:0:1::1d` | Your response must contain a list of key/value pairs of secrets that should be set on the associated Fly.io application. These secrets are available as environment variables. They aren't visible outside of an application VM. If your service is deployed on Fly, the response should also contain details about the target Fly.io application for the private IP address. See the following section for details about this. **Provisioning Response** ```javascript theme={null} { "id": "432cb1c9-4d06-4a91-95dc-bc7aa27b896d", "config": { "LOGJAM_URL": "https://user:password@test.logjam.io" }, "name": "logjam-1bd03ba", "fly_app_name": "logjam-production" } ``` Your response must return a unique ID, which could be yours or the same the one we sent you. We'll always use this ID to make requests to your API. You must also return a `config` object containing the environment variables that the provisioning should set. Optionally, you can send: * a `fly_app_name` as the target for Flycast traffic on the IP address provisioned to your extension. * an updated `name` for the extension resource, should you need to change the name we supplied due to uniqueness constraints or other reasons | Name | Type | Description | Example | | - | - | - | - | | **id** | string | A unique identifier for the resource on the provider platform | `432cb1c9-4d06-4a91-95dc-bc7aa27b896d` | | **fly\_app\_name** | string | Fly.io application name target for internal traffic | `logjam-production` | | **name** | string | Fly.io application name target for internal traffic | `logjam-1bd03ba` | ### Giving customers private access to your service If you're deploying on Fly.io, your service should be accessible to customers without exposing it to the public internet. In cases where this isn't possible or desirable, your service allow setting network restrictions to prevent internet access by default. ### Routing private traffic with Flycast Our [Flycast](/networking/flycast) internal load balancing feature allow you to route traffic from an IPv6 address on a customer network to one of your Fly.io applications. The `ip_address` field above refers to this feature. At provisioning time, we'll allocate an address on the customer network for you. Then, your reply should include the target Fly.io application where you wish that IP's traffic to be routed to. By default, no traffic is routed to your app until it has services configured. See the [Machines API](/machines/api/machines-resource#create-a-machine) and [fly.toml docs](/reference/configuration#the-services-sections) for details. Optionally, you can add the [proxy protocol handler](/networking/services#connection-handlers) to a service. We co-opt the [proxy protocol](https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt) as a way to give your service knowledge of the IP address assigned on the customer network, at client connection time. This IP is useful for performing an additional security check beyond user/password credentials or tokens. ### DNS records For a good developer experience, you should create a public DNS record for your internal service that resolves to the Flycast IP address mentioned in the previous section. This address should be used in the configuration variables in the provisioning response, i.e.: ```javascript theme={null} { "config": { "LOGJAM_URL": "https://user:password@logjam-instance-01.logjam.io" }, "fly_app_name": "logjam-instance-01", "id": "432cb1c9-4d06-4a91-95dc-bc7aa27b896d" } ``` ### Fetching extension status In most cases, we'll need to fetch information about a specific extension resource. For example, to get its current `status`, display usage information, etc. Also, if your resource cannot be provisioned synchronously, we'll need such this endpoint to poll readiness status. ### Updating Extensions Customers should be able to update some extensions directly from `flyctl`. For example, adding/removing read replicas on a database. Your API should support updates using the same parameters as the `POST` request above at: ``` PATCH {base_url}/extensions/{extension_id} ``` ### Single Sign On Flow We want to get users into your platform with as little friction as possible, directly from `flyctl`. Example: `flyctl ext logjam dashboard -a myapp` This command should log the customer in to your UI, on the extension detail screen. The CLI will open the user's browser and send a `GET` request. `GET {base_url}/extensions/{extension_id}/sso` If the user is already logged in, you should redirect them to the extension detail screen. If not, you should start an OAuth authorization request to Fly.io, by redirecting to: ``` GET https://api.fly.io/oauth/authorize?client_id=123&response_type=code&redirect_uri=https://logjam.io/flyio/callback&scope=read ``` You should pass the organization ID and desired permissions scope. Currently, only the `read` scope is supported. Once we authenticate the user, we'll redirect to your OAuth `redirect_uri` with an authorization code you may exchange for an access token via a POST request. ``` POST https://api.fly.io/oauth/token client_id=logjam client_secret=123 grant_type=authorization_code code=myauthcode redirect_uri=https://logjam.io/flyio/callback ``` The JSON response: ``` { "access_token"=>"fo1__034hk03k4mhjea0l4224hk", "token_type"=>"Bearer", "expires_in"=>7200, "refresh_token"=>"j-elry40hpy05m2qbaptr", "scope"=>"read", "created_at"=>1683733170 } ``` Finally, use this access token to issue an inline request to our [token introspection endpoint](https://www.oauth.com/oauth2-servers/token-introspection-endpoint/). The response gives you enough information to: * Authorize the user if they belong the correct parent organization in your system * Provision the user and add them to these organization * Assign a role that maps to the Fly.io `admin` or `member` roles * Send emails to the user through an email alias ``` GET https://api.fly.io/oauth/token/info Authorization: Bearer fo1__034hk03k4mhjea0l4224hk ``` The JSON response: ``` { "resource_owner_id"=>"rzjkdw3g0ypx061q", "user_id"=>"rzjkdw3g0ypx061q", "user_name": "Gabriella Fern", "email": "5mrgw4xzkxoz02yk@customer.fly.io", "organizations"=> [ { id: "zd3e5wvkjel6pgqw", role: "admin" }, { id: "zgyep87w1m4q06d4", role: "member" } ], "scope"=>["read"], "expires_in"=>7200, "application"=>{"uid"=>"logjam"}, "created_at"=>1683740928 } ``` ## Incoming Webhooks: Notify Fly.io about changes to extension resources Providers should send webhooks to Fly.io when changes happen to resources, such as: * A database transitioning from `ready` to `sleeping` * A database is removed via the provider UI * A customer changes their payment plan of a resource or an organization ### Webhook URL and format Each extension provider gets their own webhook URL, like so: `https://api.fly.io/api/hooks/extensions/logjam` This endpoint will accept `POST` requests of the `application/json` type. ### Webhook Authentication and Request Signing Webhook requests should be signed the same way as we [sign provisioning requests](/reference/extensions_api#authentication-and-request-signing) but with a different shared secret. ### The webhook request body The request must include a UNIX `timestamp`, `action` string and `resource` object. Here's an example: ``` { "timestamp": "1693513586", "action": "resource.updated", "resource": { "plan": "scaler_pro", "id": "5lgmabb3y30", "status": "ready" } } ``` Supported actions are: ``` resource.updated resource.deleted resource.created ``` For `resource.created`, the request body should include the Fly.io `organization_id` and `user_id` that provisioned the resource. ``` { "timestamp": "1693513586", "action": "resource.created", "resource": { "plan": "scaler_pro", "name": "myprod-db" "organization_id": "kg032ljbmqs0j", "user_id": "nh0kweyt23jyhl", "id": "5lgmabb3y30", "status": "ready" } } ``` Note: `resource` should contain the same parameters provided by `GET` endpoints for individual resources. ## Outbound Webhooks: Get notified about changes to provisioned accounts and resources We intend to inform your service about system changes such as: * Addition or removal of provisioned users from an organization * Provisioned user role changes * Issues with hosts that affect your deployment * Individual machine events (stops, starts, crashes, etc) **Currently, we only send machine events from a single Fly.io organization.** If your service deploys on Fly.io, we can send you webhooks for machine events, such as stops and starts, with some details about the source and cause of each. These webhooks conform to the [CloudEvents spec](https://github.com/cloudevents/spec). The spec encodes common attributes like `ID`, `source`, `type` and `timestamp` in HTTP headers. Find your [SDK of choice here](https://github.com/cloudevents/). *Type* will be encoded in this format: `io.fly.machine.starting`. The contents of `data` varies depending on the event. These types and data will be documented, eventually. For now, consult us on a case-by-case basis if the contents are not self-explanatory. Here's a sample payload, minus the fields mentioned above. ``` POST https://logjam.io/flyio/extensions/events { "machine_id": "5683977ad31768", "status": "failed", "data": { "Error": "image not found", "Transition": "prepareImage" } } ``` ## Email communication with customers The automatic signup process happening through Fly.io is less intentional than the signup one makes through a provider's website. In light of that, we want to avoid overwhelming users with messaging unrelated to their intended goal - provisioning a service. The following are guidelines, open for discussion on a case-by-case basis. Providers should discuss their desired email cadence with Fly.io customers early in the process to avoid surprises down the line. Providers shouldn't ask new users to confirm their email. Email confirmation is a requirement to use our platform, so emails can be assumed to be confirmed ahead of time. Providers should mostly send transactional messages about the resource itself, or about billing. For example: database idling, system maintenance or alerts about passing free tier limits. Providers should take care that developers do not receive duplicate emails. For example, if a user is placed on an opt-out email campaign, you should ensure the following: The same user should not receive multiple, identical emails because they happen to provision resources on more than one Fly.io organization. This is quite common, as organizations are used for separating production and staging environments, for example. Once a single user in an organization has been placed on an email campaign, other users in the same organization should not be placed on a similar campaign merely for having used SSO sign-in. ## Deploying your service on Fly.io If you run a latency-sensitive service like a database, we require you run your service on Fly.io to be considered an official extension. Contact us at [extensions@fly.io](mailto:extensions@fly.io) about deploying your service on the Fly.io platform. ## Billing customers through Fly.io Your service should offer a minimal billing API for fetching costs and passing them on to customers. We'd want to query this API at least once per day. Fly.io will bill customers for your service and pay you what's owed after deducting the revenue share. We prefer services that can provide a simple, pay-as-you-go pricing model. We also prefer that making changes to billing, support plans, etc happen inside the provider UI. ## Support The support process for Extensions is evolving and should be discussed on a case-by-case basis. ## Limitations Keep the following in mind when considering running an extension on Fly.io. The Extensions Program does *not*: * Run billing logic for your extension - we pass billing data through our invoice for convenience * Manage your service beyond the underlying Fly.io services * Run support for your service - but we will pass support requests on to you * Run workflows to deploy your service automatically - you need a control plane # Feedback API Source: https://docs.fly.io/reference/feedback-api Report a bug, a documentation problem or a missing feature to Fly.io with POST /api/v1/feedback, using any access token and a request ID or URL. The Feedback API lets you report a problem to Fly.io from a script, a CI job or a coding agent, without opening a support ticket or a forum thread. It takes a category, a note, and either the request ID of the call that went wrong or the URL of the page that was unclear. It's aimed at the cases where filing feedback by hand is the thing that stops you doing it: an agent that hits a confusing API response, or a deploy script that wants to flag a docs page that no longer matches reality. ## Endpoint ``` POST https://api.fly.io/api/v1/feedback ``` Authenticate with a Fly.io access token in an `Authorization: Bearer ` header. Create a token with [`fly tokens create`](/flyctl/cmd/fly_tokens_create) or on the [Tokens](https://fly.io/tokens) page. The token needs read access to an organization. A token scoped to one organization files the report under it; otherwise the report goes to your personal organization. The organization is what reports are attributed to and what the rate limit counts against, so a token narrowed to something else, or an expired one, is rejected. Ownership always comes from the token, never from the request body. Legacy `fo1_` OAuth tokens aren't accepted. Use a macaroon token, which starts with `fm2_`. See [Access tokens](/security/tokens). ## Request Send a JSON body with `Content-Type: application/json`. | Field | Required | Description | | - | - | - | | `category` | Yes | One of `bug`, `documentation`, `feature_request` or `other`. | | `note` | Yes | What went wrong, in up to 4,000 characters. | | `request_id` | See below | The `fly-request-id` of the request you're reporting, up to 255 characters. | | `url` | See below | The page or endpoint you're reporting, up to 2,048 characters. | Include at least one of `request_id` or `url`, so the report points at something specific. You can send both. For an API problem, `request_id` is the most useful thing you can send: every Fly.io response carries a `fly-request-id` header, and it lets us find the exact request in our logs. ```sh cmd theme={null} curl -i -X POST https://api.fly.io/api/v1/feedback \ -H "Authorization: Bearer $FLY_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "category": "bug", "note": "The response was missing a field.", "request_id": "01MANUALTEST00000000000000-gru" }' ``` ```output output theme={null} HTTP/1.1 201 Created content-type: application/json; charset=utf-8 {"already_submitted":false,"feedback_id":"eodj0v3m964wg796"} ``` ## Response | Status | Description | | - | - | | 201 | The report was filed. | | 200 | An identical report was already filed; `already_submitted` is `true`. | | 400 | A legacy `fo1_` OAuth token was used. Mint a `fm2_` token instead. | | 401 | The token was missing or invalid. | | 422 | No organization resolved from the token, or validation failed. Check the category value, and that a note and a request ID or URL are present. | | 429 | Rate limited. | A successful response carries the new report's ID: ```json theme={null} {"already_submitted":false,"feedback_id":"eodj0v3m964wg796"} ``` `already_submitted` lets a retry be safe: sending the same report twice gets a `200` and the original ID rather than filing a duplicate. ## Rate limits Feedback is limited to 20 reports per minute per organization. Over that, requests get a `429`. # Fly Launch overview Source: https://docs.fly.io/reference/fly-launch Understand what fly launch does on Fly.io: app creation, build and fly.toml config, resource provisioning, first deploy, launch outcomes, and a custom launch. Fly Launch is a bundle of features that take a lot of the work out of deploying and managing your Fly App. Two commands that you'll use often are [`fly launch`](/flyctl/cmd/fly_launch) and [`fly deploy`](/launch/deploy). The usual way to create a new Fly App is to write your project and then run `fly launch`. The `fly launch` command automates as much as possible between writing the code and deploying on Fly.io, setting you up with a running app with good defaults. The `fly launch` command performs several tasks, depending on the project: * initializes a new app in your Fly.io organization * detects known project types * configures the app with good defaults * builds the Docker image used to create app Machines * provisions resources like Postgres clusters, Redis databases, and IP addresses * deploys the app for the first time The language-specific scanners built into `fly launch` perform different tasks as needed. The following sections describe what generally happens between writing your source code and it going live on Fly.io through Fly Launch. ## New Fly App creation When `fly launch` creates a new Fly App, the app gets a name, an organization, a preferred deployment region, and a default configuration that's good for simple apps that should be publicly available on the web. ## Build configuration An app deployed on Fly.io has to be packaged into a Docker image so we can turn it into a Machine. This image can be pre-built, or it can be built during the deploy process, based on a Dockerfile (recommended), a Buildpack, [or a Nixpack](https://community.fly.io/t/build-images-with-nixpacks/6169). Build information is in the `build` section of `fly.toml`. `fly launch` will fill this in automatically if it can. You can override build information at deployment time using flags with `fly deploy`. Reference: [Builders and Fly.io](/reference/builders) ## Fly App configuration On creation, an app gets a default configuration that will work for most basic web apps. You can view an app's configuration by opening the `fly.toml` file in your project's source directory, or in JSON format by running `fly config show`. Postgres apps don't have a local `fly.toml` file, but you can download and save it locally with `fly config save -a my-app-name`. You can make configuration changes by editing an app's `fly.toml` and then running `fly deploy`. The flyctl language-specific scanners make changes to app configuration as part of their work. Reference: [App configuration (fly.toml)](/reference/configuration) ## Platform resource provisioning Before deployment, you might want to create and configure a [Fly Volume](/volumes) or [Postgres database](/unmanaged-postgres), or [set app secrets](/apps/secrets). Some flyctl scanners will do some or all of this using the API. ## Deployment The `fly launch` command might deploy your new app for the first time if you accept the defaults or after you tweak the app's settings. You can stop `fly launch` from deploying the new app right away with `fly launch --no-deploy`. Whenever you want to update your app, you'll run [`fly deploy`](/flyctl/cmd/fly_deploy). `fly deploy` creates a new release of the app with the updated configuration, and builds and deposits the image in the Fly.io registry, if needed. Public IP addresses are provisioned if the app listens on public ports and doesn't already have them. Finally, some hardware is allocated and at least one Machine is booted up. You can use `fly deploy` options to change certain elements of the app's configuration, such as adding an environment variable. The local `fly.toml` file, if any, won't be altered by this, but you can overwrite it with the currently-deployed configuration using `fly config save`, if you want to keep the changes for a future deployment. Initial CPU and RAM specs default to the smallest available. Some projects will need beefier resources to run. You can set a default Machine size and memory in the [`vm` section](/reference/configuration#the-vm-section) of the `fly.toml` file. You can also [scale](/launch/scale-machine) the app's Machines using flyctl. ## Launch outcomes If all goes well, one of several things will happen when you run `fly launch` in a project's working directory. In every case, you'll have the option to tweak the app's defaults before deployment: 1. If the `--image` flag was used, the app will be configured to use a preexisting Docker image. 2. If the `--dockerfile` flag was used, the app will be configured to use the specified Dockerfile to build the image. 3. Otherwise, flyctl scans the local working directory. 1. If it detects a project of a kind it has a full-service launcher for, it will automatically configure the app. 2. If flyctl doesn't have a launcher that can configure the whole app, but does find a Dockerfile, it will offer to deploy it using that Dockerfile for the build. 4. If none of the above happens, flyctl will register a new app with a name and organization, and download you a default `fly.toml` to work with. Deployment can't happen without further configuration. In all of these cases, `fly launch` downloads the final app config into a `fly.toml` in the working directory. `fly deploy` will look for a `fly.toml` from which to set the app's configuration on each new deployment. Once you create an app, you can make changes to it and provision further resources through `fly.toml` and flyctl commands. ## More things the scanners do The language- or framework- specific scanners incorporated into `fly launch` may also do fancy things like the following: * Download files (such as Dockerfile, config files) into your working directory. Existing local files will be overwritten only with confirmation. * Run commands in your local development environment to prepare the project for deployment. * Add commands to the Dockerfile. * Set a release command. * Set build arguments. * Print messages with further information or instructions for the user. ## What happens if I say yes to importing an existing config? Fly Launch uses the config specified in that `fly.toml` instead of the default config. But the scanners continue and may overwrite these imported settings. ## Customizing Fly Launch You can customize `fly launch` to better suit your project. The example that follows illustrates how this can work. For more information about ways to customize your launch, refer to [Custom launch](/launch/create#custom-launch) and check out all the [options](/flyctl/cmd/fly_launch) available for use with `fly launch`. ### An example of a custom launch Here's me launching my Flask app that I've written and tested using the local dev server: ```bash cmd theme={null} fly launch ``` ```out out theme={null} Scanning source code Detected a Python app Using the following build configuration: Builder: paketobuildpacks/builder:base Creating app in /Users/chris/FlyTests/hello-gunicorn-flask We're about to launch your Python app on Fly.io. Here's what you're getting: Organization: MyOrg (fly launch defaults to the personal org) Name: hello-gunicorn-flask (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? (y/N) ``` I decide I'd rather use a Dockerfile-based deployment than a buildpack, for faster deployments and more control. I hit ctrl-C to stop the launch. Conveniently, there's also already a [Dockerfile that works with this app](https://github.com/fly-apps/hello-gunicorn-flask/blob/main/Dockerfile). With a Dockerfile in my working directory, if I run `fly launch` again, the Dockerfile launcher takes over before the generic Python buildpack one has a chance to. ```bash cmd theme={null} fly launch ``` ```out out theme={null} An existing fly.toml file was found for app hello-gunicorn-flask ? Would you like to copy its configuration to the new app? No ``` I have a `fly.toml` in my working directory from cloning [the `fly-apps/hello-gunicorn-flask` repo](https://github.com/fly-apps/hello-gunicorn-flask) to my local machine, but I want to use whatever config `fly launch` gives me, so I answer `No` to that question. ``` Scanning source code Detected a Dockerfile app Creating app in /Users/chris/FlyTests/hello-gunicorn-flask We're about to launch your app on Fly.io. Here's what you're getting: Organization: MyOrg (fly launch defaults to the personal org) Name: hello-gunicorn-flask (derived from your directory name) Region: Secaucus, NJ (US) (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: (not requested) ? Do you want to tweak these settings before proceeding? (y/N) ``` There's the Dockerfile scanner taking over. I'll enter `y` to have a look at the other settings the scanner picked up from my source code. The Fly Launch web page opens and I can change some basic settings like my app name or region if I want to: * The region is where Fly Launch will create the app's Machines on first deployment; this region is the `primary_region` in the `fly.toml` file. The primary region is where flyctl will add Machines for new process groups as well. I'll keep the Secaucus, NJ (ewr) region to start. * Next, I check the port for services; this port is the `internal_port` in the [`[[http_service]]` section](/reference/configuration#the-services-sections) of the `fly.toml` file. My Flask app listens on port 4999, not the more common port 8080 (the `fly.toml` default), so my Dockerfile contains the line: ```Dockerfile theme={null} EXPOSE 4999 ``` Fly.io doesn't care about EXPOSE statements in Dockerfiles directly, because we don't actually run containers, and services via our proxy are configured in `fly.toml`, not in the Dockerfile. But the Dockerfile `fly launch` scanner catches EXPOSE if it's there, and fills in accordingly. I confirm that the port is set to 4999 via the Fly Launch the web page. * Everything else looks good, so I click **Confirm Settings**. Returning to the terminal, I can see the app getting created and the image getting built in the output (edited for brevity): ``` Waiting for launch data... Done Created app 'hello-gunicorn-flask' in organization 'personal' Admin URL: https://fly.io/apps/hello-gunicorn-flask Hostname: hello-gunicorn-flask.fly.dev Wrote config file fly.toml Validating /Users/chris/FlyTests/hello-gunicorn-flask/fly.toml Platform: machines ✓ Configuration is valid ==> Building image Remote builder fly-builder-long-glitter-7257 ready ==> Building image with Docker ... --> Pushing image done image: registry.fly.io/hello-gunicorn-flask:deployment-01HGEDDH0R7T762T90B45GZRWP image size: 141 MB Watch your deployment at https://fly.io/apps/hello-gunicorn-flask/monitoring Provisioning ips for hello-gunicorn-flask Dedicated ipv6: 2a09:5789:1::4e:b68c Shared ipv4: 66.333.124.86 Add a dedicated ipv4 with: fly ips allocate-v4 ``` Because I had an HTTP service configured, and no [public IP addresses](/networking/services), Fly Launch [provisioned the IPs on deployment](/launch/deploy#ip-addresses). ``` This deployment will: * create 2 "app" machines No machines in group app, launching a new machine Creating a second machine to increase service availability Finished launching new machines ------- NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling ------- Visit your newly deployed app at https://hello-gunicorn-flask.fly.dev/ ``` The first deployment has finished! I haven't explicitly configured [process groups](/launch/processes), so my app gets two Machines assigned to the default `app` process. With `fly status`, I can see the Machines: ```bash cmd theme={null} fly status ``` ```out out theme={null} App Name = hello-gunicorn-flask Owner = personal Hostname = hello-gunicorn-flask.fly.dev Image = hello-gunicorn-flask:deployment-01HGEDDH0R7T762T90B45GZRWP Platform = machines Machines PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED app 918543b477de83 1 ewr stopped 2023-11-29T21:17:19Z app e28697ce6d3986 1 ewr stopped 2023-11-29T20:55:33Z ``` > [!NOTE] > > **Note:** Both Machines are in a stopped state, because they were idle for a few minutes and we enable the [autostop/autostart feature](/launch/autostop-autostart) by default. I can [scale out](/launch/scale-count) by adding Machines in other regions if I want to get close to users in more corners of the world. To check that my new web app is actually working, I visit the URL provided in the output in my browser (in my case that's `hello-gunicorn-flask.fly.dev`). # Fly Proxy Source: https://docs.fly.io/reference/fly-proxy Understand how Fly Proxy routes public and Flycast traffic on Fly.io, plus load balancing, connection handlers, autostop/autostart, and fly-replay routing. Fly Proxy is the omnipresent routing layer between the public Internet and the Fly.io platform and within the global WireGuard mesh that encompasses everything that runs on Fly.io. Every Fly.io edge and worker runs Fly Proxy to do the heavy lifting of moving data around our infrastructure: managing traffic and load balancing, applying protocol handlers, and connecting to services. A summary of things Fly Proxy does: * accept client connections and match them to your app's Machines in the closest region * handle backhaul between Fly.io servers over WireGuard tunnels * route requests to private Flycast addresses in your 6PN (private network) * load balance requests to your app's Machines * handle connections for different protocols: * terminate TLS by default for most public apps * terminate TLS for Postgres connections * write PROXY protocol headers on TCP connections * autostop/autostart Machines based on traffic to your app * dynamic request routing with the `fly-replay` response header ## How Fly Proxy routes incoming public requests When one of your app's users makes a request to your app on Fly.io, the request lands on our closest edge server. Fly Proxy (on the edge server) checks out the details of the request, adds the appropriate headers, redirects HTTP traffic to HTTPS, and routes the request through the WireGuard tunnel backhaul to the nearest healthy worker server that hosts your app's Machines. Fly Proxy (on the worker this time) sends the request to a Machine running your app over a local virtual interface, so your app can do its thing. Fly Proxy handles the response back from the Machine to the worker, from the worker to the edge server, and back to the user. Fly Proxy gets all its information about apps and Machines and services from corrosion, our service catalog that stores the state of pretty much everything on the Fly.io platform. Corrosion provides gossip-based service discovery that's highly-distributed, covering all our edges and regions. ## How Fly Proxy routes requests over your private network (6PN) Apps can communicate with each other by default on your [private network](/networking/private-networking) without Fly Proxy's help, but if you want to use Fly Proxy features, then you can do that with [Flycast](/networking/flycast). When you assign a Flycast address to your app, the traffic gets routed through Fly Proxy while remaining entirely in your private network. When App 1 makes a request to App 2 in the same private network, Fly Proxy on the worker hosting App 1's Machines checks out the details of the request, adds the appropriate headers, and routes the request through the same WireGuard tunnel backhaul to the nearest healthy worker that hosts Machines for App 2. Fly Proxy on that server sends the request to a Machine running App 2. ## Load balancing Fly Proxy routes requests to individual Machines in your app using a combination of concurrency settings in your app config, current load, and closeness. For more information and an example describing how Fly Proxy balances load, see [Load balancing](/reference/load-balancing). ## Connection handlers Connection handler settings tell Fly Proxy how to convert TCP requests for specific protocols before they reach your app. Most standard web services use both `http` and `tls` handlers by default and there's an optional handler for apps that use PROXY protocol. Postgres connections get their own `pg_tls` handler. Handlers aren't mandatory; if you don't use them, then Fly Proxy just forwards TCP to your app with no changes. Learn more about [connection handlers](/networking/services#connection-handlers). ## Autostart and autostop Machines Fly Proxy can start and stop existing Machines based on incoming requests, so that your app can accommodate bursts in demand without keeping extra Machines running constantly. Fly Proxy uses the same concurrency settings for autostop/autostart as for load balancing to determine when Machines have excess capacity and can be stopped. Learn more about [how autostop/autostart works](/reference/fly-proxy-autostop-autostart) and [how to configure it](/launch/autostop-autostart). ## Dynamic request routing with `fly-replay` The `fly-replay` response header instructs Fly Proxy to redeliver (replay) the original request to another region or Machine in your app, or even another app in your organization. Your app needs to send a response with the `fly-replay` header specifying the region, Machine, or app and Fly Proxy replays the whole request as instructed. Learn more about [dynamic request routing](/networking/dynamic-request-routing). # Fly Proxy autostop/autostart Source: https://docs.fly.io/reference/fly-proxy-autostop-autostart Understand how Fly Proxy stops, suspends, and starts Machines on Fly.io using soft_limit and traffic, plus auto_stop_machines and auto_start_machines behavior. ![](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/docs-stop-start.webp?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=2e6e1524cc72164691411045c17fe262) With Fly Proxy autostop/autostart, you can scale your app for peak loads and run those Machines only when needed to save on resource costs; you [don't pay for Machine CPU and RAM](/about/pricing#stopped-fly-machines) when they're in a `stopped` or `suspended` state. Fly Proxy automatically stops (or suspends) and starts existing Machines based on incoming requests to your app, and you have the option to keep a minimum number of machines running at all times in your primary region. Autostop/autostart works for Fly Apps with a service configured in the `fly.toml` file, which generally covers publicly available apps that accept requests from the internet. But you can also use autostop/autostart for private apps by setting up services and using a Flycast private IPv6 address. See [Flycast - Private Fly Proxy Services](/networking/flycast) and [Autostop/autostart private apps](/blueprints/autostart-internal-apps). For apps that [shut down automatically when idle](/launch/autostop-autostart#apps-that-shut-down-when-idle) and don't need autostop, the Fly Proxy can still restart your app's Machines when there's traffic. Learn how to [configure autostop/autostart](/launch/autostop-autostart). ## How Fly Proxy autostop/autostart works Fly Proxy runs a process to determine whether an app has excess capacity every few minutes. > [!NOTE] > > Autostop/autostart only works on existing Machines and never creates or destroys Machines for you. The maximum number of running Machines is the number of Machines you've created for your app using `fly scale count` or `fly machine clone`. Learn more about [scaling the number of Machines](/launch/scale-count). ### Fly Proxy process to stop or suspend Machines When `auto_stop_machines` is set to `"stop"` or `"suspend"` in your `fly.toml`, the proxy looks at Machines running in a single region and uses the concurrency [`soft_limit` setting](/reference/configuration#the-http_service-section) for each Machine to determine if there's excess capacity. If the proxy decides there's excess capacity, it stops or suspends exactly one Machine. The proxy repeats this process every few minutes, stopping or suspending only one Machine per region, if needed, each time. If you have the [`kill_signal` and `kill_timeout` options](/reference/configuration#runtime-options) configured in your `fly.toml` file, then Fly Proxy uses those settings when it stops a Machine. Fly Proxy determines excess capacity per region as follows: * If there's more than one Machine in the region: * the proxy determines how many running Machines are over their `soft_limit` setting and then calculates excess capacity: `excess capacity = num of machines - (num machines over soft limit + 1)` * if excess capacity is 1 or greater, then the proxy stops or suspends one Machine * If there's only one Machine in the region: * the proxy checks if the Machine has any traffic * if the Machine has no traffic (a load of 0), then the proxy stops or suspends the Machine > [!NOTE] > > **At scale**, the rate-limited loop becomes the constraint: with thousands of Machines in a single app, the proxy can't stop them fast enough to keep most idle Machines stopped. If that's your use case (for example, [per-user dev environments](/blueprints/per-user-dev-environments)), use [one app per user](/machines/guides-examples/one-app-per-user-why) with [dynamic routing](/networking/dynamic-request-routing), or have your app shut down when idle. ### Fly Proxy process to start Machines When `auto_start_machines = true` in your `fly.toml`, the Fly Proxy restarts a Machine in the nearest region when required. Fly Proxy determines when to start a Machine as follows: * The proxy waits for a request to your app. * 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](/launch/autostop-autostart) * [Autostop/autostart private apps](/blueprints/autostart-internal-apps) * [Fly Proxy features](/reference/fly-proxy) # Health Checks Source: https://docs.fly.io/reference/health-checks Configure health checks on Fly.io: top-level, service-level TCP and HTTP, and deploy-time Machine checks in fly.toml, plus check status with fly checks list. ![Illustration by Annie Ruygt of a balloon doctor examining a machine](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/health-checks.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=d7e9859fc031254080c9e10374074c10) 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. > [!NOTE] > > **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](/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]]`](/reference/configuration#services-tcp_checks), [`[[services.http_checks]]`](/reference/configuration#services-http_checks) and [`[[http_service.checks]]`](/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]]`](/reference/configuration#services-machine_checks) and [`[[http_service.machine_checks]]`](/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)](/reference/configuration) * [Seamless/Zero-downtime deployments](/blueprints/seamless-deployments) # Fly.io reference Source: https://docs.fly.io/reference/index Fly.io reference index: architecture, regions, Machine migration, flyctl, fly.toml, Fly Proxy, autostop, health checks, builders, load balancing. Quick references for often-used resources like flyctl and `fly.toml`. Or dig a little deeper into Fly.io capabilities and how they work. ## About Fly.io * **[Architecture](/reference/architecture):** A brief overview of how Firecracker VMs, Anycast networking, Fly Proxy, and the backhaul network come together to create Fly.io's infrastructure. * **[Regions](/reference/regions):** The regions around the world where we run servers so your apps can run close to your users. * **[Machine migration](/reference/machine-migration):** How Fly.io automatically migrates your app's Machines from problematic hosts to healthy hosts. *** ## Reference docs * **[flyctl](/flyctl):** The flyctl CLI reference documentation. * **[App Configuration (fly.toml)](/reference/configuration):** The settings for configuring your app in the `fly.toml` configuration file. The app configuration includes how the app is built, its volume mounts, network services, and more. * **[Fly Launch](/reference/fly-launch):** A deeper dive into how Fly Launch creates and configures new apps. * **[Fly Proxy](/reference/fly-proxy):** Learn about how Fly Proxy routes requests, handles connections, and does load balancing. * **[Fly Proxy autostop/autostart](/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](/reference/content-encoding):** Learn how our proxy handles HTTP response compression, so you get better performance out of the box. * **[Machine Suspend/Resume](/reference/suspend-resume):** How to pause a Fly Machine in place and resume it in milliseconds. * **[Health Checks](/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 * **[Autoscaling](/reference/autoscaling):** Adjust the number of running or created Fly Machines dynamically. * **[Builders](/reference/builders):** The different ways you can assemble applications into deployable images for Fly.io. * **[Load Balancing](/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](/reference/aws-to-fly-guide):** A guide with considerations for moving from AWS to Fly.io. * **[Multiple processes inside a Fly.io app](/app-guides/multiple-processes):** The different ways to run multiple processes inside a Fly App. # Load Balancing Source: https://docs.fly.io/reference/load-balancing How Fly Proxy routes each request by soft_limit and hard_limit load and RTT closeness, with a worked example of traffic spilling across regions. [Fly Proxy](/reference/fly-proxy) routes requests to individual Machines in your apps using a combination of concurrency settings specified on your app, current load, and closeness. This page describes the details of load balancing traffic to your apps on Fly.io. ## Load balancing strategy The basic load balancing strategy is: * Send traffic to the least loaded, closest Machine * If multiple Machines have identical load and closeness, randomly choose one ### Load Fly Proxy determines load using the [concurrency settings](/reference/configuration#services-concurrency) configured for an app and the current traffic relative to those settings. The table below describes how traffic may or may not be routed to a Machine based on configured `soft_limit` and `hard_limit` values. | Machine load | What happens | | - | - | | Above `hard_limit` | No new traffic will be sent to the Machine | | 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` | > [!NOTE] > > 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. You can observe live RTT values between Fly.io regions using our [RTT app](https://rtt.fly.dev/). ## Example of load balancing for a web service We have a hypothetical web service that we know can handle 25 concurrent requests with the configured CPU and memory settings. We set the following values in our fly.toml: ```toml theme={null} [services.concurrency] type = "requests" hard_limit = 25 soft_limit = 20 ``` 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 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), `nrt` (Tokyo), `sjc` (San Jose), 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: 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`. If traffic is far above the `hard_limit` for a long period of time, Fly Proxy might start returning 503 Service Unavailable responses for requests that are not able to be routed to a Machine. # Machine migration Source: https://docs.fly.io/reference/machine-migration What happens when Fly.io moves a Machine to a new host: stop via kill_signal, volume fork, new 6PN address, and migrated=true in event logs. ![Illustration by Annie Ruygt of a figure jumping from one mountian to another](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/migrating-from-aws.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=805c99c4914a71cef1ff3c36c73019bb) 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](/blueprints/resilient-apps-multiple-machines), migration shouldn't result in any downtime for your app. ## Machine migration process We migrate Machines using the following process: 1. Acquire an internal lock to coordinate multiple Machine migrations in the Fly.io platform globally. 2. Stop a Machine. 3. Fork the Machine's volume to the destination host (if the Machine has an attached volume). 4. Start a new Machine on the destination host. 5. Release the internal lock. The following sections describe the migration process in more detail. ### 1. Acquire an internal lock We set the internal lock for migration using your network ID. All the apps in your organization are on a [private network](/networking/private-networking) by default. There's no way around it, a Machine will be stopped prior to migration. When we lock by network ID, we ensure that we don't migrate other Machines from the same app or, in fact, any other apps in your network, at the same time. This can prevent clustering or quorum issues for databases and avoid downtime for apps with multiple Machines. ### 2. Stop a Machine We send the Machine the signal configured as its `kill_signal` in `fly.toml` or the `stop_config` settings in the Machine config and then shut down. This should initiate a graceful shutdown of a running app Machine. ### Fork the Machine's volume If the Machine has an attached volume, we create an exact copy of that volume, including its data, on the destination host. The new volume will have a different volume ID than the original volume. ### 4. Start a new machine We start a new Machine on the destination host and, if applicable, attach the volume we copied in step 3. The new Machine will have the same Machine ID as the original Machine. The 6PN address will be different from the original machine. Most apps communicating on Fly.io private networks use [our internal DNS](/networking/private-networking#fly-io-internal-dns) to query for 6PN addresses and that feature will continue to work as usual. Note that apps should never rely on 6PN addresses remaining static. ### 5. Release the internal lock We release the internal lock on your private network. The process repeats if any of your apps have more Machines on the affected host. ## Check whether your machine was migrated Even though you usually don't need to take any action after we migrate a Machine, you can use the `fly machine status` command to check Event Logs for migration events. For example, the following Machine was migrated, as indicated the `migrated=true` info in the `launch` Event Log: ```bash theme={null} fly machine status --app epic-stack-691c ? Select a machine: d8dd625fed5568 crimson-grass-559 (started, region ord, process group 'app') Machine ID: d8dd625fed5568 Instance ID: 01J08P60K8NK33K9P5MQ5M4QN8 State: started VM ID = d8dd625fed5568 Instance ID = 01J08P60K8NK33K9P5MQ5M4QN8 State = started Image = epic-stack-691c:deployment-01HW81ZEKCE8GB6GV7506VCS7T Name = crimson-grass-559 Private IP = fdaa:5:a89e:a7b:152:c79c:e922:2 Region = ord Process Group = app CPU Kind = shared vCPUs = 1 Memory = 1024 Created = 2024-06-13T11:36:59Z Updated = 2024-06-13T11:37:01Z Entrypoint = Command = Volume = vol_r6gzo1p3yq83yxjv Event Logs STATE EVENT SOURCE TIMESTAMP INFO started start flyd 2024-06-13T13:37:01.36+02:00 created launch flyd 2024-06-13T13:36:59.864+02:00 migrated=true ``` # Regions Source: https://docs.fly.io/reference/regions Find every Fly.io region code, see which regions have WireGuard gateways or host Managed Postgres, and check where your app runs with fly status. ![Illustration by Annie Ruygt of servers in different regions with map markers](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/regions-new.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=6f6e61f0f8d6890f907b56f2ea688563) 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. > [!NOTE] > > Run `fly platform regions` to get a list of regions. ## Fly.io Regions You can host your apps in any of the following regions. | Region ID | Region Location | Gateway | MPG | | - | - | - | - | | ams | Amsterdam, Netherlands | ✓ | ✓ | | arn | Stockholm, Sweden | ✓ | | | cdg | Paris, France | ✓ | | | dfw | Dallas, Texas (US) | ✓ | ✓ | | ewr | Secaucus, NJ (US) | ✓ | | | fra | Frankfurt, Germany | ✓ | ✓ | | gru | Sao Paulo, Brazil | | ✓ | | iad | Ashburn, Virginia (US) | ✓ | ✓ | | jnb | Johannesburg, South Africa | | | | lax | Los Angeles, California (US) | ✓ | ✓ | | lhr | London, United Kingdom | ✓ | ✓ | | nrt | Tokyo, Japan | ✓ | ✓ | | ord | Chicago, Illinois (US) | ✓ | ✓ | | sin | Singapore, Singapore | ✓ | ✓ | | sjc | San Jose, California (US) | ✓ | ✓ | | syd | Sydney, Australia | ✓ | ✓ | | yyz | Toronto, Canada | ✓ | ✓ | * **Gateway regions:** "Gateway" regions also have WireGuard gateways, through which you connect to your organization's private network. * **MPG regions:** "MPG" regions can host [Managed Postgres](/postgres) clusters. ## Discovering your app's region View the list of Fly.io regions with [`fly platform regions`](/flyctl/cmd/fly_platform_regions). You can see which regions your app is running in with [`fly status`](/flyctl/cmd/fly_status). [Fly Volumes](/volumes) and [Fly Machines](/machines) are tied to the region they're created in. Learn more about Machine placement and regional capacity in this [guide](/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](/machines/runtime-environment) information, is visible to your app running on that instance. ## Related reading * [Scaling to multiple regions](/blueprints/resilient-apps-multiple-machines#scaling-to-multiple-regions) Read our guide for making your app resilient and globally distributed. * [Cost Management](/about/cost-management) Find out how region choice, scaling strategy, and app architecture affect your Fly.io bill. * [Machine Placement and Regional Capacity](/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](/networking/dynamic-request-routing) Check out our guide to routing requests to specific regions or fallbacks using region codes. # Machine Suspend and Resume Source: https://docs.fly.io/reference/suspend-resume Suspend Machines on Fly.io to snapshot memory and resume in hundreds of milliseconds, by hand, via Fly Proxy autostop, or the Machines API, plus requirements. **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 theme={null} # 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 theme={null} $ 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**](/reference/configuration#swap_size_mb-option) **configured** * **No** [**schedule**](/machines/flyctl/fly-machine-run#start-a-machine-on-a-schedule) **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 theme={null} 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 > [!NOTE] > > Always design for both resume and cold start paths. *** ## Snapshot behavior with suspend > [!WARNING] > > 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 *** ## Volume behavior with suspend Suspend automatically saves a machine's memory state to persistent storage. You don't need to attach a [volume](/volumes) for suspend to work, and the snapshot isn't stored on a volume. If your machine does have an attached volume, the volume and its data aren't affected by suspend and resume. It helps to think of the machine snapshot and the volume as separate things: * The **snapshot** is the saved CPU and memory state, managed by Fly. It can be discarded, for example, during a deploy, which forces a [cold start](#snapshot-behavior-with-suspend). * The **volume** is your persistent storage. Its data survives suspend, resume, and cold starts, just as it does across a normal stop and start. Even if a snapshot is discarded and the machine cold starts, the data on your volume is still there. *** ## 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 theme={null} 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. If a suspended machine has a volume, you continue paying for the volume for as long as it exists [Volume storage](/about/pricing#persistent-storage-volumes) is billed whether the machine is running, stopped, or suspended. > [!NOTE] > > Suspending a machine lowers your costs, but it does not free capacity in a region. Suspended machines still reserve their resources, so suspension is not a way to fit more machines into a capacity-constrained region *** ## Monitoring & Debugging ```bash theme={null} 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 theme={null} fly machine stop fly machine start ``` *** ## Availability Suspend works in **all Fly.io regions** as of July 2024. *** **Related reading:** * [Autostop & Autostart](/launch/autostop-autostart) * [Fly Proxy Config](/reference/fly-proxy-autostop-autostart) * [Scaling Machines](/launch/scale-count) * [Machines API](/api/machines) # Rust Framework Guides Source: https://docs.fly.io/rust/frameworks Rust framework guides for Fly.io, each deploying a simple app: Axum, Actix Web, Warp, Rocket, Poem, and Dioxus LiveView over WebSockets. Each one of the language guides will take you through creating and deploying a simple application. If you don't see a framework supported here that you'd like to run on Fly.io, [tell us in the Community](https://community.fly.io/). ## Deploy an Axum App Axum is a web application framework that focuses on ergonomics and modularity. ## Deploy an Actix Web App Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust ## Deploy a Warp App Warp is a super-easy, composable, web server framework for warp speeds. ## Deploy a Rocket App Rocket is a web framework for Rust that makes it simple to write fast, type-safe, secure web applications with incredible usability, productivity and performance. ## Deploy a Poem App Poem is a full-featured and easy-to-use web framework with the Rust programming language. ## Deploy a Dioxus Liveview App 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. # Deploy an Actix Web App Source: https://docs.fly.io/rust/frameworks/actix-web Deploy an Actix Web app to Fly.io. Clone our template or bind your own HttpServer to 0.0.0.0:8080, then fly launch builds it with cargo-chef. In this guide we’ll learn how to deploy an [Actix Web](https://actix.rs/) application on Fly.io. Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust Deploying an Actix Web app on Fly.io is easy-peasy! With the help of the [cargo chef](https://github.com/LukeMathWalker/cargo-chef), we get great build times and small images. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Actix Web server on Fly.io is to use our actix-web template: ```sh theme={null} git clone --single-branch --branch actix-web git@github.com:superfly/rust-templates.git actix-web-app cd actix-web-app fly launch --generate-name ``` ## *Deploy an Actix Web App from scratch* If you don’t already have an existing Actix Web application, you can create one with `cargo`: ```sh theme={null} cargo new actix-web-on-fly cd actix-web-on-fly ``` Then we have to add the `actix-web` dependency to the project: ```bash theme={null} cargo add actix-web ``` Now, let's create a simple app in `src/main.rs`: ```rust theme={null} use actix_web::{get, App, HttpServer, Responder}; #[get("/")] async fn hello() -> impl Responder { format!("Hello from fly.io!") } #[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(hello) }) .bind(("0.0.0.0", 8080))? .run() .await } ``` This will display a "Hello from fly.io!" message when you visit the root URL. Take note that we serve the app on port `8080`. We can confirm everything works fine by running `cargo run` and checking out `http://localhost:8080`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Actix Web app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/actix-web-on-fly We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [cargo chef](/rust/the-basics/cargo-chef). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/superfly/rust-templates) for a reference. # Deploy an Axum App Source: https://docs.fly.io/rust/frameworks/axum Deploy an Axum app to Fly.io. Clone our Axum template or serve your own Router on port 8080, and fly launch generates a cargo-chef Dockerfile. In this guide we’ll learn how to deploy an [Axum](https://docs.rs/axum/latest/axum/) application on Fly.io. Axum is a web application framework that focuses on ergonomics and modularity. Deploying an Axum app on Fly.io is really straightforward! With the help of the [cargo chef](https://github.com/LukeMathWalker/cargo-chef), we get great build times and small images. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Axum server on Fly.io is to use our axum template: ```sh theme={null} git clone --single-branch --branch axum git@github.com:superfly/rust-templates.git axum-app cd axum-app fly launch --generate-name ``` ## *Deploy an Axum App from scratch* If you don't already have an existing application, you can create one with `cargo`: ```bash theme={null} cargo new axum-on-fly cd axum-on-fly ``` Then we have to add some dependencies to the project: ```bash theme={null} cargo add axum cargo add tokio -F macros -F rt-multi-thread ``` Now, let's create a simple Axum app in `src/main.rs`: ```rust theme={null} use axum::{routing::get, Router}; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello from fly.io!" })); let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); axum::serve(listener, app).await.unwrap(); } ``` This will display a "Hello from fly.io!" message when you visit the root URL. Take note that we serve the app on port `8080`. We can confirm everything works fine by running `cargo run` and checking out `http://localhost:8080`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Axum app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/axum-on-fly We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [cargo chef](/rust/the-basics/cargo-chef). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/superfly/rust-templates) for a reference. # Deploy a Dioxus Liveview App Source: https://docs.fly.io/rust/frameworks/dioxus-liveview Deploy a Dioxus Liveview app to Fly.io. Serve server-rendered Rust UI over WebSockets with Axum, then fly launch builds and ships it to a Fly Machine. In this guide we’ll learn how to deploy a [Dioxus Liveview](https://dioxuslabs.com/learn/0.6/getting_started/) application on Fly.io. 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. Bring Liveview app to life on Fly.io is a breeze! With the help of the [cargo chef](https://github.com/LukeMathWalker/cargo-chef), we get great build times and small images. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Dioxus Liveview server on Fly.io is to use our dioxus-liveview template: ```sh theme={null} git clone --single-branch --branch dioxus-liveview git@github.com:superfly/rust-templates.git dioxus-liveview-app cd dioxus-liveview-app fly launch --generate-name ``` ## *Deploy a Dioxus Liveview App from scratch* If you don’t already have an existing Dioxus Liveview application, you can create one with `cargo`: ```sh theme={null} cargo new dioxus-liveview-on-fly cd dioxus-liveview-on-fly ``` Then we have to add some dependencies to the project: ```bash theme={null} cargo add dioxus axum cargo add dioxus-liveview cargo add tokio -F full ``` Now, let's create a simple count incrementing app in `src/main.rs`: ```rust theme={null} use axum::Router; use dioxus::prelude::*; use dioxus_liveview::LiveviewRouter; fn app() -> Element { let mut num = use_signal(|| 0); rsx! { div { "hello axum! {num}" button { onclick: move |_| num += 1, "Increment" } } } } #[tokio::main] async fn main() { let addr: std::net::SocketAddr = ([0, 0, 0 ,0], 8080).into(); let app = Router::new().with_app("/", app); println!("Listening on http://{addr}"); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); } ``` This will display a counter that can be incremented using a button. The value will be updated over a websocket. We can confirm everything works fine by running `cargo run` and checking out `http://localhost:8080`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Dioxus Liveview app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/dioxus-liveview-on-fly We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [cargo chef](/rust/the-basics/cargo-chef). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/superfly/rust-templates) for a reference. # Deploy a Poem App Source: https://docs.fly.io/rust/frameworks/poem Deploy a Poem web app to Fly.io. Start from our Poem template or a new cargo project, listen on 0.0.0.0:8080, and fly launch handles the Dockerfile. In this guide we’ll learn how to deploy a [Poem](https://docs.rs/poem/latest/poem/) application on Fly.io. Poem is a full-featured and easy-to-use web framework with the Rust programming language. Deploying a Poem app on Fly.io is simple! With the help of the [cargo chef](https://github.com/LukeMathWalker/cargo-chef), we get great build times and small images. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Poem server on Fly.io is to use our poem template: ```sh theme={null} git clone --single-branch --branch poem git@github.com:superfly/rust-templates.git poem-app cd poem-app fly launch --generate-name ``` ## *Deploy a Poem App from scratch* If you don’t already have an existing Poem application, you can create one with `cargo`: ```sh theme={null} cargo new poem-on-fly cd poem-on-fly ``` Then we have to add some dependencies to the project: ```bash theme={null} cargo add poem cargo add tokio -F macros -F rt-multi-thread ``` Now, let's create a simple app in `src/main.rs`: ```rust theme={null} use poem::{get, handler, listener::TcpListener, Route, Server}; #[handler] fn hello() -> String { format!("hello from fly.io!") } #[tokio::main] async fn main() -> Result<(), std::io::Error> { let app = Route::new().at("/", get(hello)); Server::new(TcpListener::bind("0.0.0.0:8080")) .run(app) .await } ``` This will display a "Hello from fly.io!" message when you visit the root URL. Take note that we serve the app on port `8080`. We can confirm everything works fine by running `cargo run` and checking out `http://localhost:8080`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Poem app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/poem-on-fly We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [cargo chef](/rust/the-basics/cargo-chef). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/superfly/rust-templates) for a reference. # Deploy a Rocket App Source: https://docs.fly.io/rust/frameworks/rocket Deploy a Rocket app to Fly.io. Clone our Rocket template or build a new app with cargo, and fly launch creates a cargo-chef Dockerfile for small images. In this guide we’ll learn how to deploy a [Rocket](https://rocket.rs/) application on Fly.io. Rocket is a web framework for Rust that makes it simple to write fast, type-safe, secure web applications with incredible usability, productivity and performance. Launching a Rocket app on Fly.io is painless! With the help of the [cargo chef](https://github.com/LukeMathWalker/cargo-chef), we get great build times and small images. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Rocket server on Fly.io is to use our rocket template: ```sh theme={null} git clone --single-branch --branch rocket git@github.com:superfly/rust-templates.git rocket-app cd rocket-app fly launch --generate-name ``` ## *Deploy a Rocket App from scratch* If you don’t already have an existing Rocket application, you can create one with `cargo`: ```sh theme={null} cargo new rocket-on-fly cd rocket-on-fly ``` Then we have to add the `rocket` dependency to the project: ```bash theme={null} cargo add rocket ``` Now, let's create a simple app in `src/main.rs`: ```rust theme={null} #[macro_use] extern crate rocket; #[get("/")] fn index() -> &'static str { "Hello from fly.io!" } #[launch] fn rocket() -> _ { rocket::build().mount("/", routes![index]) } ``` This will display a "Hello from fly.io!" message when you visit the root URL. Take note that we serve the app on host `127.0.0.1` and port `8000`, these are the defaults for `rocket`. When deploying the app these defaults will be overridden using environment variables which you can find in the `fly.toml`. We can confirm everything works fine by running `cargo run` and checking out `http://localhost:8000`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Rocket app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/rocket-on-fly We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [cargo chef](/rust/the-basics/cargo-chef). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/superfly/rust-templates) for a reference. # Deploy a Warp App Source: https://docs.fly.io/rust/frameworks/warp Deploy a Warp server to Fly.io. Clone our Warp template or create one with cargo new, serve it on port 8080, and ship it with fly launch and cargo-chef. In this guide we’ll learn how to deploy a [Warp](https://docs.rs/warp/latest/warp/) application on Fly.io. Warp is a super-easy, composable, web server framework for warp speeds. Deploying a Warp app on Fly.io happens at the speed of light! With the help of the [cargo chef](https://github.com/LukeMathWalker/cargo-chef), we get great build times and small images. ## *Speedrun* First, [install flyctl](/flyctl/install), the Fly.io CLI, and [sign up to Fly.io](/getting-started/sign-up-sign-in#no-account-sign-up-for-fly-io) if you haven't already. The fastest way to get a basic Warp server on Fly.io is to use our warp template: ```sh theme={null} git clone --single-branch --branch warp git@github.com:superfly/rust-templates.git warp-app cd warp-app fly launch --generate-name ``` ## *Deploy a Warp App from scratch* If you don’t already have an existing Warp application, you can create one with `cargo`: ```sh theme={null} cargo new warp-on-fly cd warp-on-fly ``` Then we have to add some dependencies to the project: ```bash theme={null} cargo add warp cargo add tokio -F full ``` Now, let's create a simple Warp app in `src/main.rs`: ```rust theme={null} use warp::Filter; #[tokio::main] async fn main() { let hello = warp::path::end().map(|| "Hello from fly.io"); warp::serve(hello) .run(([0, 0, 0, 0], 8080)) .await; } ``` This will display a "Hello from fly.io!" message when you visit the root URL. Take note that we serve the app on port `8080`. We can confirm everything works fine by running `cargo run` and checking out `http://localhost:8080`. And with that you can deploy the app! ```bash cmd theme={null} fly launch ``` ```output output theme={null} Scanning source code Detected a Warp app Warning: This organization has no payment method, turning off high availability Creating app in [redacted]/warp-on-fly We're about to launch your app on Fly.io. Here's what you're getting: Organization: Your Name (fly launch defaults to the personal org) Name: [app-name] (derived from your directory name) Region: Amsterdam, Netherlands (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: (not requested) Sentry: false (not requested) ... ==> Building image ... ==> Building image with Docker ... Watch your deployment at https://fly.io/apps/[app-name]/monitoring ... Visit your newly deployed app at https://[app-name].fly.dev/ ``` This will generate a `fly.toml` file with the configuration for your app and a Dockerfile that uses [cargo chef](/rust/the-basics/cargo-chef). Refer to the [fly.toml docs](/reference/configuration) for more configuration options. To deploy a new version of your app, simply run `fly deploy` in the project directory. You can check out the full (yet minimal) example in [this GitHub repository](https://github.com/superfly/rust-templates) for a reference. # Rust on Fly.io Source: https://docs.fly.io/rust/index Launch Rust apps on Fly.io servers around the world. Begin with the Axum speedrun, then The Basics for setting up and running production Rust apps. ![Illustration by Annie Ruygt of a crab sitting on a rock](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/rust.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=6ee563da1b91cde97d6dc932ea717f98) 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. ## Getting Started Run through the [Axum Speedrun](/rust/frameworks/axum) to get a feel for what it's like to deploy to Fly.io. ## The Basics [The Basics](/rust/the-basics) covers everything you need to set up, run, and manage production Rust apps on Fly.io. These documents also identify some common problems you may encounter and provide some pointers on how to resolve them. # Rust: The Basics Source: https://docs.fly.io/rust/the-basics Rust basics on Fly.io: install the toolchain with rustup, install flyctl and log in, then deploy most Rust frameworks with a single fly launch. These guides will help you get through the basics of setting up your Rust application with pieces of infrastructure commonly found in the wild. Most Rust frameworks follow the same flow in terms of setup and vary only in their application code. For frameworks that also have a frontend component additional steps will be required. To develop Rust applications, you will need to setup the rust toolchain. We recommend using [rustup](https://rustup.rs/) to install these tools. ## Install flyctl and log in In order to start working with Fly.io, you will need `flyctl`, our CLI app for managing apps. If you've already installed it, carry on. If not, hop over to [our installation guide](/flyctl/install). Once that's installed you'll want to [log in to Fly](/getting-started/sign-up-sign-in). For most popular frameworks you can deploy your app with just one command: ```bash theme={null} fly launch ``` # Cargo Chef Source: https://docs.fly.io/rust/the-basics/cargo-chef Build Rust apps for Fly.io with a cargo-chef Dockerfile that caches dependency builds in a separate layer and ships the binary in a slim Debian runtime image. We model the Dockerfile for Rust projects after the cargo chef project. The reason for that is that the build images are very small and the builds are quick after the initial build. The image template looks something like this: ```dockerfile theme={null} FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef WORKDIR /app FROM chef AS planner COPY . . RUN cargo chef prepare --recipe-path recipe.json FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json # Build dependencies - this is the caching Docker layer! RUN cargo chef cook --release --recipe-path recipe.json # Build application COPY . . RUN cargo build --release --bin [rust-app] # We do not need the Rust toolchain to run the binary! FROM debian:bookworm-slim AS runtime WORKDIR /app COPY --from=builder /app/target/release/[rust-app] /usr/local/bin ENTRYPOINT ["/usr/local/bin/[rust-app]"] ``` You can read more about cargo chef on the [GitHub Repository](https://github.com/LukeMathWalker/cargo-chef). # Application Security by Arcjet Source: https://docs.fly.io/security/arcjet Add Arcjet rate limiting, bot protection, and email validation to a Fly.io app with fly ext arcjet create, then the Node.js or Python FastAPI SDK. [Arcjet](https://arcjet.com) is a security layer that allows developers to protect their apps with just a few lines of code. Implement rate limiting, bot protection, email validation, and defense against common attacks. Arcjet is installed as a dependency in your application and doesn't require an agent or any additional infrastructure. The SDK is currently available for JS & Python applications, with support for other languages coming soon. ## Quick start (Next.js) A quick way to see what Arcjet can do is to deploy [the example app](https://github.com/arcjet/arcjet-example-nextjs-fly). It's also available at [example.arcjet.com](https://example.arcjet.com). Clone the GitHub repository: ```bash theme={null} git clone git@github.com:arcjet/arcjet-example-nextjs-fly.git ``` Set up a new Fly app: ```bash theme={null} fly launch --no-deploy ``` This command will generate a `Dockerfile` and a `fly.toml` for you. Create an Arcjet account and link it to your Fly app: ```bash theme={null} fly ext arcjet create ``` This will: * Create an Arcjet account linked to your Fly account. * Create an Arcjet team mapped to your Fly organization. * Create an Arcjet site for your Fly application. * Set `ARCJET_KEY` as a secret on your Fly application. Deploy to Fly: ```bash theme={null} fly deploy ``` Open your app in your browser and try the features. Review the request details in your Arcjet dashboard: ```bash theme={null} fly ext arcjet dashboard ``` ## Run locally Arcjet protections run locally as well as when deployed to Fly. This makes it easy to test and debug security rules. Assuming you have already cloned [the example](https://github.com/arcjet/arcjet-example-nextjs-fly) and linked Arcjet to your Fly app (see above): Log into your Arcjet dashboard to get the `ARCJET_KEY` for your app. ```bash theme={null} fly ext arcjet dashboard ``` Install dependencies: ```bash theme={null} npm ci ``` Rename `.env.local.example` to `.env.local` and add your Arcjet key. If you want to test the rate limiting authentication, you will also need to add an Auth.js secret and [create a GitHub OAuth app](https://authjs.dev/guides/configuring-github). Start the dev server ```bash theme={null} npm run dev ``` Open [http://localhost:3000](http://localhost:3000) in your browser. ## Protecting your application with Arcjet Once you have set your `ARCJET_KEY` secret, you can start using Arcjet to protect your application. Arcjet supports many different web frameworks e.g. [Bun](https://docs.arcjet.com/get-started/bun), [Next.js](https://docs.arcjet.com/get-started/nextjs), [Node.js](https://docs.arcjet.com/get-started/nodejs), [SvelteKit](https://docs.arcjet.com/get-started/sveltekit). ### Node.js example You can also find this example [on GitHub](https://github.com/arcjet/arcjet-js/tree/main/examples/nodejs-rl). 1. Install the Arcjet SDK: ```bash theme={null} npm i @arcjet/node ``` 2. Add a rate limit to a route in `index.ts`: ```ts theme={null} import arcjet, { tokenBucket } from "@arcjet/node"; import http from "node:http"; const aj = arcjet({ key: process.env.ARCJET_KEY!, // Set as a secret on your Fly app rules: [ // Create a token bucket rate limit. Other algorithms are supported. tokenBucket({ mode: "LIVE", // will block requests. Use "DRY_RUN" to log only characteristics: ["userId"], // track requests by a custom user ID refillRate: 5, // refill 5 tokens per interval interval: 10, // refill every 10 seconds capacity: 10, // bucket maximum capacity of 10 tokens }), ], }); const server = http.createServer(async function ( req: http.IncomingMessage, res: http.ServerResponse, ) { const userId = "user123"; // Replace with your authenticated user ID const decision = await aj.protect(req, { userId, requested: 5 }); // Deduct 5 tokens from the bucket console.log("Arcjet decision", decision); if (decision.isDenied()) { res.writeHead(429, { "Content-Type": "application/json" }); res.end( JSON.stringify({ error: "Too Many Requests", reason: decision.reason }), ); } else { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello world" })); } }); server.listen(8000); ``` 3. Run your application: ```bash theme={null} npx tsx --env-file .env.local index.ts ``` Load the application and refresh the page a few times to see the rate limit in action. ### Python + FastAPI example You can also find this example [on GitHub](https://github.com/arcjet/examples/tree/main/examples/fastapi). 1. Install the Arcjet SDK: ```bash theme={null} mkdir arcjet-fastapi cd arcjet-fastapi uv init uv add arcjet "fastapi[standard]" uvicorn ``` 2. Create a new file at `main.py` with the contents: ```python theme={null} import os from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from arcjet import ( arcjet, shield, detect_bot, token_bucket, Mode, BotCategory, ) app = FastAPI() aj = arcjet( key=os.environ["ARCJET_KEY"], # Get your key from https://app.arcjet.com rules=[ # Shield protects your app from common attacks e.g. SQL injection shield(mode=Mode.LIVE), # Create a bot detection rule detect_bot( mode=Mode.LIVE, allow=[ BotCategory.SEARCH_ENGINE, # Google, Bing, etc # Uncomment to allow these other common bot categories # See the full list at https://arcjet.com/bot-list # BotCategory.MONITOR, # Uptime monitoring services # BotCategory.PREVIEW, # Link previews e.g. Slack, Discord ], ), # Create a token bucket rate limit. Other algorithms are supported token_bucket( # Tracked by IP address by default, but this can be customized # See https://docs.arcjet.com/fingerprints # characteristics: ["ip.src"], mode=Mode.LIVE, refill_rate=5, # Refill 5 tokens per interval interval=10, # Refill every 10 seconds capacity=10, # Bucket capacity of 10 tokens ), ], ) @app.get("/") async def hello(request: Request): # Call protect() to evaluate the request against the rules decision = await aj.protect( request, requested=5, # Deduct 5 tokens from the bucket ) # Handle denied requests if decision.is_denied(): status = 429 if decision.reason.is_rate_limit() else 403 return JSONResponse( {"error": "Denied", "reason": decision.reason.to_dict()}, status_code=status, ) # Check IP metadata (VPNs, hosting, geolocation, etc) if decision.ip.is_hosting(): # Requests from hosting IPs are likely from bots, so they can usually be # blocked. However, consider your use case - if this is an API endpoint # then hosting IPs might be legitimate. # https://docs.arcjet.com/blueprints/vpn-proxy-detection return JSONResponse( {"error": "Denied from hosting IP"}, status_code=403, ) return {"message": "Hello world", "decision": decision.to_dict()} ``` 3. Run your application with your Arcjet key in the environment: ```bash theme={null} export ARCJET_ENV=development export ARCJET_KEY=ajkey_yourkey uv run uvicorn main:app --reload ``` ## Pricing See [the Arcjet pricing page](https://arcjet.com/pricing). ## Support Email: [support@arcjet.com](mailto:support@arcjet.com) Discord: [Join](https://arcjet.com/discord). See the Arcjet docs for the [full support policy](https://docs.arcjet.com/support). # Security Source: https://docs.fly.io/security/index Fly.io security docs: org roles, SSO, member offboarding, TLS termination, Arcjet, access tokens, OpenID Connect, and shared responsibility. Securing a public cloud platform like Fly.io is a hard problem, and we take it seriously. The Fly.io platform comes with built-in security like hardware isolation, [private networking](/networking/private-networking) over WireGuard, and [TLS termination](/networking/services#tls-handler). *** ![Illustration by Annie Ruygt of a phoenix jumping with a motor bike](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/security.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=ffa84004df5daa604fa2144bcf7d8ff2) ## Organization and app security *Security for customer organizations and apps.* * **[Organization Roles and Permissions](/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](/security/sso):** Set up org-wide Single Sign-on with Google or GitHub. * **[Remove a member from an organization](/security/remove-org-member):** Remove a user from an organization and take steps to help keep the organization secure. * **[Built-in TLS termination](/security/tls-termination):** You get TLS termination by default for your web apps. #### Security extensions *Security add-ons from our extension partners.* * **[Application Security by Arcjet](/security/arcjet):** Use the Arcjet security layer to protect your JavaScript app with just a few lines of code. #### Tokens *Control access to your Fly.io organizations, apps, and Machines with tokens.* * **[Access tokens](/security/tokens):** Use tokens to manage access to organizations and apps. * **[OpenID Connect](/security/openid-connect):** Use OpenID Connect (OIDC) to manage access to 3rd party services. *** ## Fly.io platform security *Fly.io corporate security, compliance, and shared responsibility.* * **[Shared responsibility model](/security/shared-responsibility):** An overview of the separation of responsibilities for security on Fly.io. * **[Fly.io security practices and compliance overview](/security/security-at-fly-io):** Learn about our security practices for the Fly.io platform. *** ## Reporting a vulnerability If you believe you have found a vulnerability in any part of Fly.io, email [**security@fly.io**](mailto:security@fly.io). If you need an encrypted channel, ask and we will set one up. Our machine-readable contact file is at [fly.io/.well-known/security.txt](https://fly.io/.well-known/security.txt). What to include: the affected component or hostname, steps to reproduce, and what you were able to access. Do not access, modify, or exfiltrate customer data beyond what is needed to demonstrate the issue, and do not test against other customers' applications. What to expect from us: we acknowledge reports within 3 business days and tell you when the issue is fixed. We do not run a bug bounty program and do not publish reporter acknowledgments. We ask that you give us a reasonable window to fix the issue before publishing details, and we will tell you if we need more time. For security questions that are not vulnerability reports, the same address works. # OpenID Connect Source: https://docs.fly.io/security/openid-connect Use OpenID Connect on Fly.io to give Machines short-lived tokens for AWS, Azure, or GCP, customize the aud claim, and write an AWS trust policy to read S3. ![Illustration by Annie Ruygt of a key hole revealing a pink sky](https://mintcdn.com/fly-io/izaS1l2UMuz6sm1H/images/key-hole.png?fit=max\&auto=format\&n=izaS1l2UMuz6sm1H\&q=85\&s=bd02a1186950674f33527c6086f1bef3) Fly Machines sometimes need to access 3rd-party services or cloud providers like AWS, Azure, or GCP. To authenticate against these 3rd parties, your Machines need to supply credentials such as a password or token to the cloud provider; usually these are stored as `fly secrets` and then passed into your Machine's environment variables. However this approach involves creating hardcoded long-lived credentials on the 3rd party platform and involves either managing a lot of tokens or sharing the same token across multiple apps. Using OpenID Connect (OIDC) allows for a more secure approach by using short-lived access tokens directly from 3rd party platforms. The only caveat is that the 3rd party platform needs to support OIDC tokens and you need to establish a trust between Fly.io and the 3rd party. Providers which currently support OIDC include Amazon Web Services, Azure, Google Cloud Platform, and HashiCorp Vault, among others. ## Benefits of OpenID Connect (OIDC) Adopting OIDC to manage access to 3rd party services allows your apps to easily use good security practices like: * No hardcoded credentials, these are often shared across Machines and apps. OIDC allows for every Machine to have its own credential. * Granular control over Machines access to 3rd party resources through leveraging the authentication (authN) and authorization (authZ) tools of the 3rd party. * Rotating credentials: credentials issued from 3rd parties are only valid for 15 minutes before they expire. ## Understanding OpenID Connect Every request to the token endpoint generates a unique Json Web Token (JWT). When you give this JWT to a 3rd party they validate the token against the OIDC configuration hosted at the endpoint in the issuer (`iss`) claim. Below is an example of an issued OIDC token. A useful feature here is that the subject (`sub`) claim references the Machine's org name, app name and Machine name. This allows for regex checks to be built against the claim which can restrict accepted tokens to a specific app or Machine. ```json theme={null} { "app_id": "11111111", "app_name": "example-app", "aud": "https://fly.io/example-org", "exp": 1712099653, "iat": 1712099053, "image": "docker-hub-mirror.fly.io/you/image:latest", "image_digest": "sha256:2c1cdaded1b3820020c9dc9fdd1d6e798d6f6ca36861bb6ae64019fad6be9ee3", "iss": "https://oidc.fly.io/example-org", "jti": "93ca09e1-70e0-477b-a260-1d8fcd4ef4f4", "machine_id": "148e21ea7e46e8", "machine_name": "example-machine", "machine_version": "01HTGGC1TZ2JHK83J4AC0R3VET", "nbf": 1712099053, "org_id": "11111111", "org_name": "example-org", "region": "sea", "sub": "example-org:example-app:example-machine" } ``` ## Customizing OIDC token claims Customizing tokens allows for powerful authorization clauses in the platform you're authenticating against. For example, with AWS, you can require that a role can only be assumed by tokens which match certain `aud` and `sub` claims. This allows you to lock down and provide credentials only to a specific Fly.io app or Machine. You can customize the audience (aud) claim of your tokens by providing a value for aud in the body of the POST request. This lets you specify the recipient of the token. This is what it looks like in a curl request with the `aud` claim set to `sts.amazonaws.com`. ```bash theme={null} curl --unix-socket /.fly/api -X POST "http://localhost/v1/tokens/oidc" --data '{"aud":"sts.amazonaws.com"}' ``` ## OpenID Connect Trust Policies in AWS You can leverage the trust policy of an IAM role to restrict which Machines in your organization can assume a role in AWS. For example the following policy would only allow Machines in the app `foo-bar` within your org to assume the role it’s attached to. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456123456:oidc-provider/oidc.fly.io/" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.fly.io/:aud": "sts.amazonaws.com", }, "StringLike": { "oidc.fly.io/:sub": "org-name:foo-bar:*" } } } ] } ``` ## Reading from an S3 Bucket on a Fly Machine To read from an S3 bucket using a Fly Machine you'll first need to set up a trust between AWS and Fly.io by creating an OIDC provider in AWS and then attaching a trust policy to the role policy your Machine will assume. If you've already packaged up your app in a Dockerfile, then you can follow these steps to read from an S3 bucket: 1. Create a Fly.io app by running `fly launch` in the same directory as your Dockerfile. 2. Find the slug for the organization you want to launch your app in. ```bash theme={null} fly orgs list ``` 3. Create an [OpenID Connect Identity provider](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/identity_providers/create) in your AWS account with your org slug and the following settings: ``` Provider URL: https://oidc.fly.io/ Audience: sts.amazonaws.com ``` 4. Create an [IAM Role](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/roles/create) with the following settings: * Trusted Identity: `Web Identity -> Identity Provider -> oidc.fly.io` * Select the `AmazonS3ReadOnlyAccess` policy. 1. Set the `AWS_ROLE_ARN` as an environment variable in your `fly.toml`. ```toml theme={null} [env] AWS_ROLE_ARN = "arn:aws:iam:::role/" ``` 2. Deploy your app and let the AWS SDK do the rest. ```bash theme={null} fly deploy ``` **Notes**: If you’re setting this up for your personal org the slug is a little harder to find but you can find it in the url when you click on “Apps” on the [dashboard](https://fly.io/dashboard) # Organization Roles and Permissions Source: https://docs.fly.io/security/org-roles-permissions Member vs Admin roles in a Fly.io organization, and how to limit risk by splitting dev and prod into separate orgs and using read-only tokens. ## 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) 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](/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](/flyctl/cmd/fly_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](/apps/move-app-org) * [Handing over an application](/apps/app-handover-guide) * [Read-only tokens reference](/flyctl/cmd/fly_tokens_create_readonly) * [Staging and production isolation](/blueprints/staging-prod-isolation) # Remove a member from your organization Source: https://docs.fly.io/security/remove-org-member Offboard a user from a Fly.io org: remove them in the dashboard, delete WireGuard peers, revoke tokens, unset secrets, and rotate passwords. You can remove a user from your organization through the dashboard. This guide provides further steps to help keep your organization secure. 1. **Remove the user from the organization.** Go to **Teams** and select **Remove** from the **Options** dropdown for the user.\*\* After you remove a user, it's a good idea to go through the following steps to remove any lingering VPN tunnels, tokens, and secrets set by the user. You can also see these steps in your dashboard under **Offboarding**. 2. **Delete all WireGuard peer connections that belong to the user.** When you remove a user from an organization, all of their automatically-generated WireGuard peers get deleted. You might still need to remove WireGuard peers they created manually. List all the WireGuard peers for your org and copy the peer name to remove: ``` fly wireguard list ``` Remove the peer: ``` fly wireguard remove ``` Repeat for each peer to remove. 3. **Revoke all tokens generated by the user.** List the tokens for your organization and copy the token IDs: ``` fly tokens list --scope org ``` List the tokens for individual apps and copy the token IDs: ``` fly tokens list --app ``` Revoke all the user's tokens: ``` fly tokens revoke ... ``` 4. **Remove the secrets (environment variables) set by the user.** List the secrets in an app: ``` fly secrets list --app ``` Unset all the secrets set by the user: ``` fly secrets unset --app ... ``` Repeat for each application the user had access to. 5. **Rotate all passwords that the user had access to that could cause a security breach.** 6. **Remove the user from any other tools, such as GitHub, Slack, Jira, Trello, or Asana.** # Fly.io security practices and compliance Source: https://docs.fly.io/security/security-at-fly-io See how Fly.io handles corporate, infrastructure, and application security, its vulnerability remediation timeframes, SOC2 Type 2, and how to report issues. ![Illustration by Annie Ruygt of a dark magic book with headline Fly Security](https://mintcdn.com/fly-io/v-XOGeVK6s4rQ-4y/images/dark-arts.png?fit=max\&auto=format\&n=v-XOGeVK6s4rQ-4y\&q=85\&s=faf24b67f37bb5be70358eef124b5258) > [!NOTE] > > **Report an issue**: If you have a security concern, or believe you’ve found a vulnerability in any part of our infrastructure, please contact us. You can reach us at [**security@fly.io**](mailto:security@fly.io), and we can provide you with a Signal number if needed to convey sensitive information. ## Corporate Security (“CorpSec”) CorpSec is the practice of making sure Fly.io team members have secure access to Fly.io company infrastructure, and that secured channels are the only exposed channels to Fly.io. CorpSec controls are the primary concern of standards like SOC2. * Access to our services and applications is gated on an SSO Identity Provider. * We require strong, phishing-resistant 2FA in all enrolled IdP accounts. * We rely on IdP-backed WireGuard with strict, default-deny, role-based access controls to access internal applications. * We have centralized repositories of policy and audit controls, including team onboard and offboarding and access requests; we regularly audit access to internal systems. ## Process Controls: Network/Infrastructure Security (“InfraSec”) InfraSec is the practice of ensuring a hardened, minimal attack surface for components we deploy on our network. Conventionally, modern InfraSec centers on “cloud security”; of course, we are ourselves a cloud provider, which makes the job more interesting. * We run on our own hardware deployed in secure data centers like Equinix. * Our platform networking runs over a WireGuard mesh with further BPF-based access controls. Everything is encrypted in transit, at multiple layers. * Remote management is largely automated, and fully audited; remote access is done through an IdP-backed cert-based SSH system with transcript-level audit trails. * Fly.io operates a large logging and metrics cluster (it’s a feature of our platform!). * Our internal client/server services are locked down further with Mutual TLS. * We work with upstream traffic providers to perform automated and manual DDoS mitigation. * Compute jobs at Fly.io are virtualized using Firecracker, the virtualization engine developed at AWS as the engine for Lambda and Fargate. * Customer information on databases and volumes at Fly.io is encrypted with the Linux LUKS block storage encryption secrets. ## Application Security (“AppSec”) AppSec is the practice of ensuring software is secure by design, secured during development, secured with testing and review, and securely deployed. * Our software is built in memory-safe programming languages, including Rust (for our Anycast forwarding path) and Golang (for our deployment and control plane).  * We make decisions that minimize our attack surface. Most interactions with Fly.io are well-described in a GraphQL API, and occur through flyctl, our open-source command-line tool. * We perform internal code reviews with a modern, PR-based development workflow, and engage external testing firms to assess our software security. ## Vulnerability Remediation Vulnerabilities that directly affect Fly.io's systems and services will be patched or otherwise remediated within a timeframe appropriate for the severity of the vulnerability, subject to the public availability of a patch or other remediation instructions. **Severity: Timeframe** * Critical: 24 hours * High: 1 week * Medium: 1 month * Low: 3 months * Informational: As necessary If there's a severity rating that accompanies a vulnerability disclosure, we'll generally rely on that as a starting point, but may upgrade or downgrade the severity in our best judgement. ## SOC2 and HIPAA [We have our SOC2 Type 2](https://fly.io/blog/soc2-the-screenshots-will-continue-until-security-improves/) where we've documented a bunch of these controls. Additionally, we've detailed a number of controls for folks exploring [running HIPAA-compliant applications on our platform](/about/healthcare). ## Questions? [Email us!](mailto:security@fly.io) # Shared responsibility model Source: https://docs.fly.io/security/shared-responsibility Fly.io's shared responsibility model: what Fly secures, like workers, edges, and Fly Proxy, versus what you own in your image, tokens, and SSO. Here at Fly.io, we want your apps to be secure. As part of that, we take a large helping of things off your plate and worry about them for you. Here is a high-level, non-exhaustive overview of the things we take responsibility for on our platform, and the parts that you, the customer, are responsible for yourself. ## Brief infrastructure overview The three main types of systems that are involved in delivering your service are workers, edges, and gateways. A worker hosts Fly Machines, an edge proxies incoming network traffic and delivers it to a worker, a gateway terminates WireGuard connections that provide access to your private network, or 6PN. There's also a lot of software involved, but the important ones are flyd which manages Fly Machines on a worker, [Fly Proxy](/reference/fly-proxy) which is the frontend proxy service that runs on edges, and flyctl which runs on your client devices. ## Fly.io's responsibilities ### Hardening and protection of infrastructure systems Fly.io is solely responsible for the security of our infrastructure systems, including workers, edges and gateways. This includes managing access, applying security patches to the operating system, other 3rd-party software (e.g. firecracker), and mitigating hardware vulnerabilities where appropriate. Fly.io monitors these systems in both an operational and security capacity. ### Using secure software development processes Fly.io ensures that any software we are responsible for developing meets an appropriate level of security. This includes ensuring the security of the software's supply chain, conducting regular audits and testing, as well as developing and maintaining a positive security culture within engineering teams. ### Security of the underlying platform features involved in delivering your service Where Fly.io exposes a feature for your applications to use, we are responsible for the security of that features implementation. Two clear examples are: * We provide HTTP Proxy termination as a platform feature, therefore we are responsible for the security of the HTTP and TLS protocols. e.g. we are responsible for fixing protocol-level vulnerabilities such as HTTP response splitting, and H2 Rapid Reset. * We provide encrypted private networking to your applications, we are responsible for the security of that layer. For example, if Wireguard had a confidentiality issue, it would be our responsibility to address that in our infrastructure. ### Provision and security of platform controls Fly.io provides features that you can use to secure your account and its assets, including authentication (e.g. tokens, SSO) and authorization (macaroons, roles). Fly.io is responsible for providing and ensuring the effectiveness of these controls, as well as provisioning secure default configurations. Fly.io was built with security front and centre. Visit our [Security](https://fly.io/security) page to learn more. ## Your responsibilities ### Everything within your Docker image and therefore Fly Machines We generally consider our demarcation point the virtualization boundary between our worker (virtualization host) and the Fly Machine (virtualization guest). The Fly Machine is constructed from your instructions, and therefore its contents are your responsibility. This includes: the system userspace and libraries, your application code, your application environment, and the services you run. That being said, Fly.io is in a unique place to help you stay on top of things here, and we are committed to shipping features, such as container scanning, to make this painless for you. ### Configuration of security features of Fly.io Fly.io commits to providing secure defaults for platform features and configurations, and to thoroughly document and warn about any footguns that may lurk within. Ultimately, however, you are responsible for correct configuration of Fly.io's security features to restrict access as appropriate to Fly.io hosted resources. For example, membership of a Fly organization, and network services exposed by a Fly application. ### Security of client devices and authentication material You are responsible for the security of the devices from which you use flyctl, the Machines API, or any other method to access Fly.io. You are also responsible for securing any authentication material used for accessing the Fly.io platform and services, including access tokens, Macaroons, WireGuard peer keys, and SSH keys. ### Security of your identity provider Where Fly.io is configured to perform single sign-on (SSO) against your identity provider (and we strongly recommend that you do so), it is your responsibility to correctly configure and maintain that provider to ensure that only authorized users can access your Fly.io resources. ### Application-layer network security of your services Two specific callouts: the Fly Proxy and the 6PN (private network). Fly.io is responsible for ensuring Fly Proxy is a secure server to your users, and a secure client to your service. You must ensure your service is a secure server to Fly Proxy, and that any sensitive application functionality is properly protected. Fly.io is responsible for the isolated network-layer for your applications, which protects confidentiality. You are responsible for the security of your applications on that network. For example, [protection from SSRF attacks](https://portswigger.net/web-security/ssrf) from other applications that may share that network segment. # Single sign-on for organizations Source: https://docs.fly.io/security/sso Require Google Workspace or GitHub single sign-on for a Fly.io organization from the dashboard, and how org SSO differs from user login. Organizations are administrative entities on Fly.io that let you to manage billing, control access by adding and removing members, and share app development environments. You can, and usually should, require members of your organizations to use SSO. Fly.io supports Google and GitHub as Identity Providers, and you can use one or both. If you already use Google or GitHub, enabling SSO takes advantage of their built-in security, like 2FA/MFA and passkeys. ## Authenticating user accounts vs. organizations An individual Fly.io user account can belong to multiple organizations, each with different access requirements. Users continue log in to Fly.io with their usual username/password or SSO. When you set up SSO as a requirement for an organization, users will need to re-authenticate to access org-specific resources and information. ## Set up SSO using Google workspace access From the Fly.io [dashboard](https://fly.io/dashboard/), select an organization from the dropdown. Go to **Settings > Single Sign On > Google SSO Requirement**. Enter the Google Hosted Domain for SSO, check **Enable**, and click Save. ## Set up SSO using GitHub organization access Install the [Fly.io GitHub app](https://fly.io/app/auth/github_app) on the GitHub organization you want to use for SSO. From the Fly.io [dashboard](https://fly.io/dashboard/), select an organization from the dropdown. Go to **Settings > Single Sign On > GitHub SSO Requirement**. Enter the GitHub organization ID for SSO, check **Enable**, and click Save. ## Related topics * [Use organizations for staging and production isolation](/blueprints/staging-prod-isolation) # TLS termination by Fly Proxy Source: https://docs.fly.io/security/tls-termination Fly Proxy terminates TLS on port 443 using Let's Encrypt or imported certificates. Remove the tls handler to terminate TLS in your own app. Fly Proxy provides TLS termination by default for web apps (services accepting traffic over HTTPS on port 443). With the built-in TLS handler, Fly Proxy uses your app's TLS certificates to terminate TLS connections, converts them to unencrypted TCP, and forwards traffic to your app through our secure WireGuard mesh network. Fly Proxy supports both Fly-managed certificates issued through Let's Encrypt, and your own uploaded certificates. To set up certificates for a custom domain, including importing your own certificates, see [Custom domains](/networking/custom-domain). You can [configure a specific TLS version and ALPN protocols](/reference/configuration#services-ports-tls_options) for your app in the `fly.toml` config file. For supported versions and cipher suites, see [TLS support](/networking/tls). If you want to terminate TLS yourself, then you only need to remove the handlers from your services in `fly.toml` or in your Machine config and we'll forward TCP directly to your app. # Access tokens Source: https://docs.fly.io/security/tokens Create Fly.io access tokens scoped to one app or org, including SSH, machine-exec and read-only tokens, then set expiry, list and revoke them with fly tokens. Whether you're deploying your app with GitHub Actions or running your own CD service, it's best to avoid configuring deployment infrastructure with all-powerful tokens. Our access tokens use macaroons, which come with predefined scopes to reduce access from org-wide all the way down to running a specific command on a single app's Machines. If you want all the details, you can read about our decision to use macaroons in our [API Tokens: A Tedious Survey](https://fly.io/blog/api-tokens-a-tedious-survey/#macaroons) and [Macaroons escalated quickly](https://fly.io/blog/macaroons-escalated-quickly/) blog posts. > [!NOTE] > > **Important:** We've previously suggested in docs and community that you should use the output of the `fly auth token` command as your API token for everything; this is no longer true. The auth token, sometimes called the "personal access token" or the "all-powerful auth token", is a very short-lived token that is automatically created each time you log in with `fly auth login`. It's used by flyctl to create, manage, configure, and deploy all the apps in any organization in your account. You should use tokens created with `fly tokens deploy` or `fly tokens org` instead. It's always a good idea to use the token with the narrowest access that will work for your purpose. You can create Fly.io access tokens with different predefined scopes using flyctl. ## App-scoped tokens Use an app-scoped token, sometimes just called a "deploy token", to limit access to a single app. App-scoped tokens are useful for CI/CD pipelines where you need to share a token with a 3rd party. You can create a standard app-scoped deploy token, a token to SSH into an app's Machines, or a token to execute commands on an app's Machines. ## Org-scoped tokens Use an org-scoped token for access to manage all the apps within a single organization. An org-scoped token is the middle ground between the auth token and the more restricted app-scoped token, and is useful when you want to automate a single org. You can create a standard org-scoped token or a read-only org-scoped token. ## Create and manage tokens Create and manage tokens using flyctl. `fly tokens create` commands have some useful options for customizing your token: * **token names:** If you're managing multiple tokens, then give the tokens custom names with the `--name` option to make them easier to find when you list them with flyctl or view them in your dashboard. * **token expiry:** When you create a token, include the duration that the token is valid for with the `--expiry` option. You should specify the shortest possible duration for your use case so that you limit access by time as well as scope. Tokens are valid for 20 years (175200h0m0s) by default. For all options and commands, refer to the [`fly tokens` command docs](/flyctl/cmd/fly_tokens). ### Create app-scoped tokens App-scoped tokens are limited to managing a single app and its resources. Some organization-wide features like managing WireGuard tunnels are integral to deployments and are also accessible to deploy tokens. If you don't specify a name with the `--name` option, then the default name is `flyctl deploy token`. Create an app-scoped deploy token with a custom name and expiry: ``` fly tokens create deploy --name <"my token name"> --expiry ``` This example creates an app-scoped deploy token called `staging one` that's valid for 48 hours: ``` fly tokens create deploy --name "staging one" --expiry 48h ``` #### Create an app-scoped token for SSH only Create a token to SSH into a single app. The SSH token is scoped to only allow SSH access to a specific app and nothing else. To be able to SSH to an app, this token is also allowed to connect to the org’s WireGuard network. For example: ``` flyctl tokens create ssh -a my-app > my-app.token.ssh ``` Use the token to SSH into the app: ``` FLY_API_TOKEN=$(cat my-app.token.ssh) flyctl ssh console -a my-app ``` #### Create an app-scoped token to execute commands on Machines A machine-exec token can execute a restricted set of commands on an app's Machines. You can specify commands on the command line or with the `--command` and `--command-prefix` options. If no command is provided, all commands are allowed. Create a machine exec token: ``` fly tokens create machine-exec --command "" ``` ### Create org-scoped tokens Org-scoped tokens are limited to managing a single org and its resources, including apps. If you don't specify a name with the `--name` option, then the default name is `Org deploy token`. Create an org-scoped deploy token with a custom name and duration: ``` fly tokens create org --name <"my token name"> --expiry ``` This example creates an org-scoped deploy token called `prod` that's valid for one week: ``` fly tokens create org --name "prod" --expiry 168h ``` #### Create an org-scoped read-only token You can further limit an org-scoped deploy token by making it read-only, which limits the token access to reading a single org and its resources. If you don't specify a name with the --name option, then the default name is Read-only org token. Create a read-only org-scoped deploy token with a custom name and expiry: ``` fly tokens create readonly --name <"my token name"> --expiry ``` Create a read-only token based on an existing token: ``` fly tokens create readonly --name <"my token name"> --expiry --from-existing -t ``` ### List or view tokens List all the tokens for current app, or use the `--app` option to specify an app: ``` fly tokens list ``` List all the tokens in an org, including org-scoped tokens: ``` fly tokens list --scope org ``` ### Revoke tokens 1. List the tokens and copy the ID of the token to revoke. Include the `--scope org` to list org-scoped tokens. ``` fly tokens list ``` 2. Revoke the token: ``` fly tokens revoke ``` ### Manage tokens in the dashboard To manage app-scoped tokens, click an app, then click **Tokens**. From here you can revoke tokens or create new tokens. To manage org-scoped tokens, choose an organization from the dropdown, then click **Tokens**. From here you can revoke tokens or create new tokens. # Create Checkpoint Source: https://docs.fly.io/sprites/api/checkpoints/create-checkpoint /sprites/api/openapi.json post /v1/sprites/{name}/checkpoint Create a new checkpoint of the current sprite state. Returns streaming NDJSON progress. # Get Checkpoint Source: https://docs.fly.io/sprites/api/checkpoints/get-checkpoint /sprites/api/openapi.json get /v1/sprites/{name}/checkpoints/{checkpoint_id} Get details of a specific checkpoint. # List Checkpoints Source: https://docs.fly.io/sprites/api/checkpoints/list-checkpoints /sprites/api/openapi.json get /v1/sprites/{name}/checkpoints List all checkpoints. # Restore Checkpoint Source: https://docs.fly.io/sprites/api/checkpoints/restore-checkpoint /sprites/api/openapi.json post /v1/sprites/{name}/checkpoints/{checkpoint_id}/restore Restore to a specific checkpoint. Returns streaming NDJSON progress. # Authorize an OAuth connector Source: https://docs.fly.io/sprites/api/connectors/authorize-an-oauth-connector /sprites/api/openapi.json get /v1/oauth/{provider}/authorize Create an OAuth authorization URL for a provider. Use this to start a browser-based consent flow. # Complete OAuth authorization Source: https://docs.fly.io/sprites/api/connectors/complete-oauth-authorization /sprites/api/openapi.json post /v1/oauth/{provider}/callback Exchange an OAuth authorization code for an encrypted connector. # Create an API-key connector Source: https://docs.fly.io/sprites/api/connectors/create-an-api-key-connector /sprites/api/openapi.json post /v1/oauth/connections/api_key Create a connector from a user-provided API key for providers that support key validation. # Delete a connector Source: https://docs.fly.io/sprites/api/connectors/delete-a-connector /sprites/api/openapi.json delete /v1/oauth/connections/{id} Delete a connector and its encrypted credentials. # Get a connector Source: https://docs.fly.io/sprites/api/connectors/get-a-connector /sprites/api/openapi.json get /v1/oauth/connections/{id} Get a single connector by ID. # List connectors Source: https://docs.fly.io/sprites/api/connectors/list-connectors /sprites/api/openapi.json get /v1/oauth/connections List connectors configured for the authenticated organization. # Provision a connector Source: https://docs.fly.io/sprites/api/connectors/provision-a-connector /sprites/api/openapi.json post /v1/oauth/connections/provision Provision a system-managed connector for providers that support platform-managed keys. # Update a connector Source: https://docs.fly.io/sprites/api/connectors/update-a-connector /sprites/api/openapi.json put /v1/oauth/connections/{id} Update a connector access policy. # Update a connector Source: https://docs.fly.io/sprites/api/connectors/update-a-connector-1 /sprites/api/openapi.json patch /v1/oauth/connections/{id} Update a connector access policy. # Execute Command Source: https://docs.fly.io/sprites/api/exec/execute-command /sprites/api/openapi.json post /v1/sprites/{name}/exec Execute a command via simple HTTP POST (non-TTY only). Simpler alternative for exec for environments that can't handle websockets. # Kill Exec Session Source: https://docs.fly.io/sprites/api/exec/kill-exec-session /sprites/api/openapi.json post /v1/sprites/{name}/exec/{session_id}/kill Kill an exec session by session ID. Returns streaming NDJSON with kill progress. # List Exec Sessions Source: https://docs.fly.io/sprites/api/exec/list-exec-sessions /sprites/api/openapi.json get /v1/sprites/{name}/exec List active exec sessions. # Change File Mode Source: https://docs.fly.io/sprites/api/filesystem/change-file-mode /sprites/api/openapi.json post /v1/sprites/{name}/fs/chmod Change file or directory permissions. # Change File Owner Source: https://docs.fly.io/sprites/api/filesystem/change-file-owner /sprites/api/openapi.json post /v1/sprites/{name}/fs/chown Change file or directory ownership. # Copy File or Directory Source: https://docs.fly.io/sprites/api/filesystem/copy-file-or-directory /sprites/api/openapi.json post /v1/sprites/{name}/fs/copy Copy a file or directory. # Delete File or Directory Source: https://docs.fly.io/sprites/api/filesystem/delete-file-or-directory /sprites/api/openapi.json delete /v1/sprites/{name}/fs/delete Delete a file or directory. # List Directory Source: https://docs.fly.io/sprites/api/filesystem/list-directory /sprites/api/openapi.json get /v1/sprites/{name}/fs/list List directory contents. # Read File Source: https://docs.fly.io/sprites/api/filesystem/read-file /sprites/api/openapi.json get /v1/sprites/{name}/fs/read Read file contents from the sprite filesystem. Returns raw file bytes. # Rename File or Directory Source: https://docs.fly.io/sprites/api/filesystem/rename-file-or-directory /sprites/api/openapi.json post /v1/sprites/{name}/fs/rename Rename or move a file or directory. # Write File Source: https://docs.fly.io/sprites/api/filesystem/write-file /sprites/api/openapi.json put /v1/sprites/{name}/fs/write Write file contents to the sprite filesystem. Request body contains raw file bytes. # Delete Privileges Policy Source: https://docs.fly.io/sprites/api/network-policy/delete-privileges-policy /sprites/api/openapi.json delete /v1/sprites/{name}/policy/privileges Remove privileges policy to revert to default (unrestricted) behavior. # Delete Resources Policy Source: https://docs.fly.io/sprites/api/network-policy/delete-resources-policy /sprites/api/openapi.json delete /v1/sprites/{name}/policy/resources Remove resources policy to revert to default behavior. # Get Network Policy Source: https://docs.fly.io/sprites/api/network-policy/get-network-policy /sprites/api/openapi.json get /v1/sprites/{name}/policy/network Get the current network policy configuration. # Get Privileges Policy Source: https://docs.fly.io/sprites/api/network-policy/get-privileges-policy /sprites/api/openapi.json get /v1/sprites/{name}/policy/privileges Get the current privileges policy configuration (capability and device restrictions). # Get Resources Policy Source: https://docs.fly.io/sprites/api/network-policy/get-resources-policy /sprites/api/openapi.json get /v1/sprites/{name}/policy/resources Get the current resources policy configuration (memory limits). # Set Network Policy Source: https://docs.fly.io/sprites/api/network-policy/set-network-policy /sprites/api/openapi.json post /v1/sprites/{name}/policy/network Update the network policy configuration. # Set Privileges Policy Source: https://docs.fly.io/sprites/api/network-policy/set-privileges-policy /sprites/api/openapi.json post /v1/sprites/{name}/policy/privileges Update the privileges policy configuration to restrict capabilities or devices. # Set Resources Policy Source: https://docs.fly.io/sprites/api/network-policy/set-resources-policy /sprites/api/openapi.json post /v1/sprites/{name}/policy/resources Update the resources policy configuration to set memory limits. # Create Service Source: https://docs.fly.io/sprites/api/services/create-service /sprites/api/openapi.json put /v1/sprites/{name}/services/{service_name} Create or update a service definition. # Get Service Source: https://docs.fly.io/sprites/api/services/get-service /sprites/api/openapi.json get /v1/sprites/{name}/services/{service_name} Get details of a specific service. # Get Service Logs Source: https://docs.fly.io/sprites/api/services/get-service-logs /sprites/api/openapi.json get /v1/sprites/{name}/services/{service_name}/logs Stream logs for a service. # List Services Source: https://docs.fly.io/sprites/api/services/list-services /sprites/api/openapi.json get /v1/sprites/{name}/services List all configured services and their current state. # Restart Service Source: https://docs.fly.io/sprites/api/services/restart-service /sprites/api/openapi.json post /v1/sprites/{name}/services/{service_name}/restart Restart a service (stop if running, then start). Returns streaming NDJSON with stop and start progress. # Start Service Source: https://docs.fly.io/sprites/api/services/start-service /sprites/api/openapi.json post /v1/sprites/{name}/services/{service_name}/start Start a service. Returns streaming NDJSON with stdout/stderr. # Stop Service Source: https://docs.fly.io/sprites/api/services/stop-service /sprites/api/openapi.json post /v1/sprites/{name}/services/{service_name}/stop Stop a running service. Returns streaming NDJSON with service stop progress. # Create a sprite Source: https://docs.fly.io/sprites/api/sprites/create-a-sprite /sprites/api/openapi.json post /v1/sprites Create a new sprite with a unique name in your organization # Delete a sprite Source: https://docs.fly.io/sprites/api/sprites/delete-a-sprite /sprites/api/openapi.json delete /v1/sprites/{name} Delete a sprite and all associated resources # Get a sprite Source: https://docs.fly.io/sprites/api/sprites/get-a-sprite /sprites/api/openapi.json get /v1/sprites/{name} Get details for a specific sprite # List sprites Source: https://docs.fly.io/sprites/api/sprites/list-sprites /sprites/api/openapi.json get /v1/sprites List all sprites for the authenticated organization # Update a sprite Source: https://docs.fly.io/sprites/api/sprites/update-a-sprite /sprites/api/openapi.json put /v1/sprites/{name} Update sprite settings such as URL authentication # Attach to Exec Session Source: https://docs.fly.io/sprites/api/websockets/attach-to-exec-session Attach to an existing exec session via WebSocket. # Control Channel Source: https://docs.fly.io/sprites/api/websockets/control-channel Multiplexed control channel for sequential operations (exec, proxy) over a single persistent WebSocket connection. Reduces connection overhead for clients sending many operations. # Execute Command Source: https://docs.fly.io/sprites/api/websockets/execute-command Execute a command in the sprite environment via WebSocket. Commands continue running after disconnect; use `max_run_after_disconnect` to control timeout. Supports TTY and non-TTY modes. # TCP Proxy Source: https://docs.fly.io/sprites/api/websockets/tcp-proxy Establish a WebSocket tunnel to a port inside the sprite environment. # Watch Filesystem Source: https://docs.fly.io/sprites/api/websockets/watch-filesystem Watch for filesystem changes via WebSocket. # Watch Ports Source: https://docs.fly.io/sprites/api/websockets/watch-ports Watch for port open/close events across all processes in the sprite namespace. On connect, sends a snapshot of all currently listening ports as a port_list message, then streams incremental port_opened/port_closed events. # CLI Authentication Source: https://docs.fly.io/sprites/cli/authentication Sign the sprite CLI in with your Fly.io account, switch orgs, keep tokens in the keyring, and use sprite auth setup --token for CI like GitHub Actions. Sprites uses your Fly.io account for authentication. This guide covers setting up authentication and managing API tokens. ## Quick Setup The simplest way to authenticate: ```bash theme={null} sprite login ``` This opens a browser window where you sign in to Fly.io and approve the CLI. Once complete, you're ready to use Sprites. ## Authentication Flow ### 1. Authenticate with Fly.io ```bash theme={null} sprite login ``` This signs you in to Fly.io in the browser, then adds an API token for your organization. You don't need to be logged into `flyctl` first. If the browser doesn't open, open the link the command prints. To log in to a specific organization: ```bash theme={null} sprite login -o my-team ``` ### 2. Select an Organization If you belong to multiple Fly.io organizations, select one: ```bash theme={null} sprite org list ``` Output: ``` Organizations: personal (current) my-team another-org ``` To add another organization: ```bash theme={null} sprite org auth --org my-team ``` ## Token Management You can also create and manage tokens at [sprites.dev/account](https://sprites.dev/account). ### Viewing Your Token Your Sprites API token is stored securely. To view it: ```bash theme={null} # If using keyring storage, disable it first sprite org keyring disable # Then check the config file cat ~/.sprites/sprites.json ``` The token is used for API and SDK authentication. ### Token Storage By default, tokens are stored in your system keyring for security. You can switch to file-based storage: ```bash theme={null} # Disable keyring (stores token in config file) sprite org keyring disable # Re-enable keyring storage sprite org keyring enable ``` ### Config File Structure When using file-based storage, `~/.sprites/sprites.json` contains: ```json theme={null} { "version": "1", "current_selection": { "url": "https://api.sprites.dev", "org": "personal" }, "urls": { "https://api.sprites.dev": { "url": "https://api.sprites.dev", "orgs": { "personal": { "name": "personal", "keyring_key": "sprites-cli:", "use_keyring": true, "sprites": {} } } } } } ``` ## Multiple Organizations ### Adding Organizations After you log in with `sprite login`, add tokens for more organizations: ```bash theme={null} # Add another organization sprite org auth --org my-team ``` ### Switching Organizations ```bash theme={null} # List organizations sprite org list # Use a specific org for a command sprite -o my-team list # Set default organization sprite org set-default my-team ``` ### Per-Directory Organization Use different organizations in different directories: ```bash theme={null} cd ~/projects/team-project sprite use --org my-team my-sprite cd ~/projects/personal sprite use --org personal my-personal-sprite ``` The organization is saved in the local `.sprite` file. ## Logging Out ### Remove a Single Organization ```bash theme={null} sprite org logout --org my-team ``` ### Remove All Credentials ```bash theme={null} sprite logout ``` This removes all stored tokens and configuration. ## CI/CD Authentication For CI/CD or automated environments, use `sprite auth setup --token` to configure authentication: ```bash theme={null} # Set up authentication from a token (no browser needed) sprite auth setup --token "my-org/token-id/secret" ``` You can generate tokens at [sprites.dev/account](https://sprites.dev/account). ### GitHub Actions Example ```yaml theme={null} - name: Install Sprites CLI run: | curl -fsSL https://sprites.dev/install.sh | bash echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Setup Sprites auth run: sprite auth setup --token "$SPRITES_TOKEN" env: SPRITES_TOKEN: ${{ secrets.SPRITES_TOKEN }} - name: Use Sprites run: sprite list ``` ### Environment Variables After authentication is configured, you can use environment variables to override settings: ```bash theme={null} export SPRITE_ORG="my-org" # Override the default organization export SPRITES_API_URL="https://api.sprites.dev" # Override API endpoint ``` ## Troubleshooting ### "Not authenticated" Error ```bash theme={null} # Re-authenticate sprite login ``` ### Token Not Found If the CLI can't find your token: ```bash theme={null} # Check if using keyring sprite org keyring disable # Verify token exists cat ~/.sprites/sprites.json | grep api_token # Re-authenticate if needed sprite login ``` ### Browser Doesn't Open If the authentication browser doesn't open automatically: 1. Look for the URL printed in the terminal 2. Copy and paste it into your browser 3. Complete authentication 4. Return to the terminal ### Permission Errors If you get permission errors after authentication: 1. Verify your Fly.io account has access to the organization 2. Check which organizations the CLI has tokens for: `sprite org list` 3. Try re-authenticating: `sprite login` ## Security Best Practices 1. **Use keyring storage** (default) for interactive use 2. **Use `sprite auth setup --token`** for CI/CD pipelines with tokens stored as secrets 3. **Never commit** `.sprites/` or `.sprite` files to version control 4. **Rotate tokens** periodically by re-authenticating 5. **Use organization-specific tokens** rather than personal tokens in shared environments ## Next Steps > **[Commands Reference](/sprites/cli/commands)** > > Full CLI documentation and command examples > **[Quickstart](/sprites/quickstart)** > > Create your first Sprite in minutes # CLI Commands Reference Source: https://docs.fly.io/sprites/cli/commands Reference for each sprite CLI command and flag: org, create, use, list, destroy, exec, console, checkpoint, restore, proxy and url, plus exit codes. Complete reference for all `sprite` CLI commands. ## Authentication Commands ### `sprite login` sprite login - Authenticate with Fly.io ```bash theme={null} sprite login [flags] [api-url] ``` **Options:** * `-o, --org ` - Specify organization * `-s, --sprite ` - Specify sprite * `-h, --help` - Show this help message **Examples:** ```bash theme={null} sprite login sprite login -o my-org sprite login https://custom-api.sprites.dev Related Commands: sprite auth --help Non-interactive token setup (CI/CD) sprite org --help Manage organizations and tokens after login ``` ### `sprite logout` sprite logout - Remove Sprites configuration ```bash theme={null} sprite logout [flags] ``` **Options:** * `-h, --help` - Show this help message **Examples:** ```bash theme={null} sprite logout ``` ### `sprite org auth` sprite org auth - Add an API token ```bash theme={null} sprite org auth [-o :] [api-url|alias] ``` **Aliases:** `sprite orgs`, `sprite organizations`, `sprite o` **Options:** * `-o, --org ` - Specify organization * `-h, --help` - Show this help message **Examples:** ```bash theme={null} sprite org auth # Use default API sprite org auth https://custom-api.sprites.dev # Custom API URL sprite org auth prod # Use 'prod' alias sprite org auth -o myorg # Specific organization sprite org auth -o prod:my-org # Alias with org override sprite org auth https://staging-api.sprites.dev -o staging:test-org ``` ### `sprite org list` sprite org list - Show configured tokens ```bash theme={null} sprite org list [api-url|alias] ``` **Options:** * `-h, --help` - Show this help message ### `sprite org logout` sprite org logout - Remove all tokens ```bash theme={null} sprite org logout [flags] ``` **Options:** * `--force` - Skip confirmation prompt * `-h, --help` - Show this help message ### `sprite org keyring disable` sprite org keyring disable - Disable keyring usage ```bash theme={null} sprite org keyring disable ``` **Options:** * `-h, --help` - Show this help message **Examples:** ```bash theme={null} sprite org keyring disable ``` ### `sprite org keyring enable` sprite org keyring enable - Enable keyring usage ```bash theme={null} sprite org keyring enable ``` **Options:** * `-h, --help` - Show this help message ### `sprite auth setup` sprite auth setup - Set up authentication using a pre-generated token. ```bash theme={null} sprite auth setup --token ``` **Options:** * `--token ` - Token in format: org-slug/org-id/token-id/token-value * `-h, --help` - Show this help message **Examples:** ```bash theme={null} sprite auth setup --token "org-slug/org-id/token-id/token-value" ``` ## Sprite Management ### `sprite create` sprite create - Create a new sprite ```bash theme={null} sprite create [flags] [sprite-name] ``` **Options:** * `-o, --org ` - Specify organization * `-s, --sprite ` - Specify sprite * `--skip-console` - Exit after creating instead of connecting to console * `--label