How to use PlanetScale with Phoenix on Fly.io

6th June, 2026

I had a small Elixir Phoenix app that I wanted to deploy somewhere for testing. I reached for Fly.io because I’ve played around with the service before and found it interesting. Unfortunately Fly don’t offer the cheap good-enough-for-testing self-managed Postgres they used to. They only offer Managed Postgres at Managed Postgres prices now.

I decided to give PlanetScale a go.

The next step should be as simple as as setting the DATABASE_URL correctly, right? Unfortunately not.

These are the configuration changes I made to get connected to PlanetScale from my Phoenix app. You may have to make additional changes to get your app fully deployed.

Deploy the app

First off, I started the process of deploying my app to Fly.

We’re going to set the DATABASE_URL.

fly secrets set DATABASE_URL="postgresql://<username>:<password>@aws-eu-west-2-1.pg.psdb.cloud:5432/postgres?sslmode=verify-full&sslrootcert=system"

Now, in the app directory:

fly launch

This will generate a Dockerfile and other config needed for deploying to Fly. It won’t be quite right though, so we’ll need to tweak it. The deploy is going to fail.

Configure SSL

Postgres on PlanetScale is SSL only, so we need to enable that in config/runtime.exs.

   maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []

   config :example_app, ExampleApp.Repo,
-    # ssl: true,
+    ssl: true,
     url: database_url,
     pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
     # For machines with several cores, consider starting multiple pools of `pool_size`
     # pool_count: 4,
     socket_options: maybe_ipv6

IPv6 configuration

When Fly configures your app it sets it up to work with their own internal networking, which uses IPv6 – very modern. This doesn’t work when we are trying to connect to PlanetScale though.

We need to tell Ecto to not use IPv6 in rel/env.sh.eex. This file will have been generated by fly launch.

 #!/bin/sh

 if [ -n "$FLY_APP_NAME" ]; then
   export DNS_CLUSTER_QUERY="${FLY_APP_NAME}.internal"
   export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}"
   # configure node for distributed erlang with IPV6 support
   export ERL_AFLAGS="-proto_dist inet6_tcp"
+  export ECTO_IPV6="false"
 fi

Database pool size

Now, that should technically be it for getting us connected to the database, but I got errors due to the amount of database connections. I just wanted this app to work, so I lowered the number in config/runtime.exs and it worked.

You should likely not follow this advice.

   maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []

   config :example_app, ExampleApp.Repo,
     ssl: true,
     url: database_url,
-    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
+    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "2"),
     # For machines with several cores, consider starting multiple pools of `pool_size`
     # pool_count: 4,
     socket_options: maybe_ipv6

Additionally, it should be noted that I am connecting directly to Postgres in this example, not via PlanetScale’s PgBouncer setup, which uses port other than 5432.

This should get you connected.