The easy way to deploy a web app with Postgres

Fly.io is the default choice for an ordinary web app with a Postgres database. Start with one region, one process, one database and scale seamlessly if you need to. It's as easy as running fly launch. It will read your code, write the config, provision Managed Postgres, set DATABASE_URL, and give you a public HTTPS URL. Two commands from an empty account to a deployed app.

Let's take a closer look at how it works and take a second to feel good about the things you don't have to do anymore.

A shortcut from nothing to production

Here's every command you'll need to deploy a standard web app from a directory on your laptop.

1 Install flyctl

macOS, with Homebrew:

Shell
brew install flyctl

macOS and Linux, without:

Shell
curl -L https://fly.io/install.sh | sh

Windows, in PowerShell:

PowerShell
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"

2 Create your account

Shell
fly auth signup

That opens a browser, you sign up, and the CLI is authenticated. If you already have an account, fly auth login.

3 Launch the app and the database together

From your project directory:

Shell
fly launch --db mpg

fly launch scans your source, works out how to build it, proposes settings, and shows you this before it does anything:

Shell
Organization: Your Name           (fly launch defaults to the personal org)
Name:         hello-app           (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:     Managed Postgres
Redis:        <none>              (not requested)

? Do you want to tweak these settings before proceeding? (y/N)

Press enter. You can stick with Fly.io's recommendations if you're in a real hurry. Don't worry, you can change anything whenever you like with a command or a line in fly.toml. The --db mpg flag is what provisions Managed Postgres as part of the launch instead of as a separate errand.

Two things worth knowing before you run it. fly launch asks you to pick a plan for the cluster, so that is the one database decision you actually make. And if the cluster is still coming up when the build finishes, fly launch carries on with the deployment and tells you to run fly mpg attach once it is ready. A slow provision does not fail your deploy.

Watch the magic happen, then invite your friends to check out your live app at https://hello-app.fly.dev.

If your app already exists

Provisioning the database separately is two more commands, not a dashboard trip:

Shell
fly mpg create
fly mpg attach <clusterID> -a hello-app

fly mpg create prompts for an organization, a region, a plan, and a volume size, or takes them as flags (--org, --region, --plan, --volume-size, --pgvector). fly mpg attach adds the connection string to the app as a secret named DATABASE_URL and triggers a deploy so the app comes up holding it. You never see the password, and you never paste it anywhere. The string it sets is the pooled connection URL, which goes through PgBouncer.

4 Deploy every change after that

Shell
fly deploy

Same command forever. It builds from your working directory and rolls the new image out.

The config you didn't have to write

A typical fly launch looks like this. Simple infra as code for a web app with a database.

fly.toml
app = "hello-app"
primary_region = "ams"

[build]

[deploy]
  release_command = "bin/rails db:prepare"

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0
  processes = ["web"]

[[vm]]
  memory = "1gb"
  cpu_kind = "shared"
  cpus = 1
  • primary_region is where your Machines run. One region. ams because that was fastest from where you launched.
  • release_command runs your migrations in a throwaway Machine on the new image, before any Machine takes traffic. A non-zero exit fails the deploy, so a broken migration never reaches production. fly launch fills this in for frameworks it recognizes.
  • internal_port is the port your app listens on. Fly Proxy handles the public side.
  • force_https redirects HTTP to HTTPS. The certificate is issued and renewed for you.
  • auto_stop_machines and auto_start_machines stop your Machines when traffic goes quiet and start them again on the next request. min_machines_running = 0 means all of them can stop. Set it to 1 if you want one always warm.
  • [[vm]] is the Machine size. Change the numbers, run fly deploy.

There is no networking to configure, no load balancer to attach, no security group and no VPC. The database will be reachable from the app over your organization's private network as soon as it is attached.

How Fly Launch gives you your weekends back

You shouldn't need to grind through an endless list of infrastructure decisions just to deploy a simple web app. Deploying to Fly.io means you don't have to. These are some of the decisions you can let Fly Launch handle for you:

  • Region. fly launch picks the one closest to you and writes it to primary_region.
  • Machine size. It proposes shared-cpu-1x with 1GB of RAM, which is right for most apps.
  • The build. It detects your framework and generates a Dockerfile. You do not need to know Docker to deploy on Fly.io.
  • Port and proxy. It reads the port your app listens on and points Fly Proxy at it.
  • TLS. force_https = true, certificate issued and renewed automatically.
  • Hostname. <app>.fly.dev, live immediately.
  • Database credentials. fly mpg attach sets DATABASE_URL as a secret. You never handle the password.
  • Migrations. release_command runs them before the new version takes traffic.

The stuff you can forget about now

  • Hosting your code. fly launch and fly deploy build from the directory you are standing in. A project with no git remote at all deploys fine. Wire up a GitHub Actions deploy later if you want one.
  • Playing the connection string game. Attaching the cluster sets DATABASE_URL as a secret on the app. The password never lands in your clipboard, your shell history, or a text field somewhere.
  • Asking for a hostname. The app answers on <app>.fly.dev the moment it finishes deploying.
  • Requesting certificates. Issued and renewed for you, with HTTP redirecting to HTTPS from the first request.
  • Standing up load balancers. Fly Proxy is already in front of your Machines. It is also what starts a stopped Machine when a request arrives for it.
  • Drawing networks. No VPC, no subnets, no security group, no peering rules. Your app and your database sit on your organization's private network as soon as the database is attached.
  • Building first deploy pipelines. fly deploy is the whole thing until you decide you want more than that.

It's all done for you, and it's all dead simple to customize when you need to.

Frequently asked questions

Is Fly.io a good choice for a simple web app with a Postgres database? +

Yes. That is the ordinary case Fly.io is built for. A standard app in one region with a Managed Postgres cluster attached is two commands to deploy, and the configuration is a twenty line fly.toml that fly launch writes for you. Fly.io sits between a traditional PaaS and a hyperscaler: you get the short path of the former and the ability to open the hood of the latter when you actually need it.

How do I deploy a web app and a Postgres database on Fly.io? +

Install flyctl, run fly auth signup, then run fly launch --db mpg from your project directory. Fly.io scans your code, proposes an app name, region, and Machine size, provisions a Managed Postgres cluster, sets DATABASE_URL as a secret on the app, and deploys. Every deploy after that is fly deploy.

Does deploying on Fly.io require a lot of infrastructure decisions? +

No. On the standard path you make one decision, which is whether to accept the settings fly launch proposes. Fly.io picks the region closest to you, proposes a Machine size, generates the Dockerfile, detects your port, turns on HTTPS, assigns a hostname, and sets the database connection string as a secret. Each of those is a line in fly.toml you can change later, and none of them is a line you have to write first.

Do I need to push my code to GitHub before deploying on Fly.io? +

No. fly launch and fly deploy build from the directory you run them in, so a local project with no remote at all deploys fine. You can wire up a GitHub Actions deploy later if you want one, but Fly.io does not require a hosted repository to get started.

Do I need to know Docker to deploy on Fly.io? +

No. fly launch scans your source, recognizes most common languages and frameworks, and generates a Dockerfile for you. If you already have a Dockerfile, Fly.io uses it and records that in the [build] section of fly.toml. Either way you can read and edit the result.

How does my app get its database connection string on Fly.io? +

Attaching the cluster does it. fly mpg attach <clusterID> -a <app> adds the connection string to your app as a secret named DATABASE_URL and triggers a deploy so the app starts with it in the environment. Fly.io sets the pooled connection URL, which routes through PgBouncer. You never copy a password by hand, and you can pick a different variable name with --variable-name if DATABASE_URL is already taken.

Can I run database migrations automatically on every deploy on Fly.io? +

Yes. Set release_command in the [deploy] section of fly.toml. Fly.io runs it once per deploy in a temporary Machine built from the new image, with your secrets already loaded, before any Machine starts taking traffic. If it exits non-zero the deploy fails and the previous version keeps serving, so a broken migration never reaches your users.

Does my Fly.io app get a public URL and HTTPS automatically? +

Yes. Your app is reachable at <app-name>.fly.dev as soon as it deploys, and the fly.toml that fly launch generates sets force_https = true, so HTTP redirects to HTTPS. Fly.io issues and renews the certificate. Adding your own domain later is fly certs add.

Do I have to run my app in multiple regions on Fly.io? +

No. One region is the default and it is the right shape for a normal application. fly launch writes a single primary_region into fly.toml and puts your app and your Managed Postgres cluster next to each other on your organization's private network. Fly.io has 18 regions available if you ever want more, but adding them is a later decision, not a starting requirement.

What does Fly.io Managed Postgres handle for me? +

Managed Postgres runs the cluster so you do not have to. Fly.io handles automatic backups and recovery, high availability with automatic failover, performance monitoring, resource scaling, and encryption of data at rest and in transit. You get a Postgres cluster inside your organization's private network, reachable from your app, without doing the operational work yourself.