Deploy an Elixir Phoenix Application
Getting an application running on Fly 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 infrastructure to run on the global application platform.
In this guide we'll learn how to deploy an Elixir Phoenix application on Fly and connect it to a PostgreSQL database.
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 previous deployment guide.
We'll be using the standard web application generated by the Elixir Phoenix Framework.
Preparation
If you don't already have Elixir and Phoenix installed, get them set up before starting this guide. When you install Elixir, the Mix build tool ships with it.
You can install the Phoenix project generator phx.new
from a Hex package as follows:
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, your Fly app command center, and sign up to Fly if you haven't already.
Now let's generate a shiny, new Phoenix app:
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 we run fly launch
from the newly-created project directory, the launcher will:
- Ask you to select a deployment region
- Set secrets required by Phoenix (
SECRET_KEY_BASE
, for example) - Run the Phoenix deployment setup task
- Optionally setup a Postgres instance in your selected region
- Deploy the application in your selected region
cd hello_elixir
fly launch
Creating app in /Users/me/hello_elixir
Scanning source code
Detected a Phoenix app
? App Name (leave blank to use an auto-generated name): hello_elixir
? Select organization: flyio (flyio)
? Select region: mad (Madrid, Spain)
Created app hello_elixir in organization soupedup
Set secrets on hello_elixir: SECRET_KEY_BASE
Installing dependencies
Running Docker release generator
Wrote config file fly.toml
? Would you like to setup a Postgres database now? Yes
Postgres cluster hello_elixir-db created
Username: postgres
Password: <password>
Hostname: hello_elixir-db.internal
Proxy Port: 5432
PG Port: 5433
Save your credentials in a secure place, you will not be able to see them again!
Monitoring Deployment
1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 2 total, 2 passing]
--> v0 deployed successfully
Connect to postgres
Any app within the flyio organization can connect to postgres using the above credentials and the hostname "hello_elixir-db.internal."
For example: postgres://postgres:password@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/
Postgres cluster hello_elixir-db is now attached to hello_elixir
Would you like to deploy now? Yes
Deploying hello_elixir
==> Validating app configuration
--> Validating app configuration done
Services
TCP 80/443 ⇢ 8080
Remote builder fly-builder-little-glitter-8329 ready
...
...
That's it! Run fly open
to see your deployed app in action.
Try a few other commands:
fly logs
- Tail your application logsfly status
- App deployment detailsfly status -a hello_elixir-db
- Database deployment detailsfly deploy
- Deploy the application after making changes
Storing Secrets on Fly.io
You may also have some secrets you'd like set on your app.
Use fly secrets
to configure those.
fly secrets set MY_SECRET_KEY=my_secret_value
Deploying Again
When you want to deploy changes to your application, use fly deploy
.
fly deploy
Note: On Apple Silicon (M1) computers, docker runs cross platform builds using qemu which might not always work. If you get a segmentation fault error like the following:
=> [build 7/17] RUN mix deps.get --only
=> => # qemu: uncaught target signal 11 (Segmentation fault) - core dumped
You can use fly's remote builder by adding the --remote-only
flag:
fly deploy --remote-only
You can always check on the status of a deploy
fly status
Check your app logs
fly logs
If everything looks good, open your app on Fly.io!
fly open
Important IPv6 Settings
The flyctl
command attempts to modify your project's Dockerfile and append the following lines:
# 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...
IEx Shell Into Your Running App
Elixir supports getting a IEx shell into a running production node. How cool is that?
To do this, we will login with SSH to our application VM. There are a couple one-time setup tasks for using SSH. Follow the instructions.
fly ssh establish
fly ssh issue --agent
With SSH configured, let's open a console.
fly ssh console
Connecting to hello_elixir.internal... complete
/ #
If all went smoothly, you have a shell to the machine!
Now we just need to launch our remote IEx shell. The command is named for your application. For this example it is:
app/bin/hello_elixir remote
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!
(Tip: One of several ways to exit the IEx shell is to hit Ctrl+C, Ctrl+C
; to log out of the VM console, use Ctrl+D
or exit
.)
With another quick update we can prepare our application for clustering by naming our nodes differently. That's next!
Naming Your Elixir Node
In your Elixir application, run this command:
mix release.init
Then edit the generated rel/env.sh.eex
file and add the following lines:
ip=$(grep fly-local-6pn /etc/hosts | cut -f 1)
export RELEASE_DISTRIBUTION=name
export RELEASE_NODE=$FLY_APP_NAME@$ip
This names our Elixir node using the Fly application name and the internal IPv6 address. Make sure to deploy after making this change!
fly deploy
Nice! Our application is ready for clustering!
Clustering Your Application
Elixir and the BEAM have the incredible ability to be clustered together and processes can pass messages seamlessly to each other between nodes. Fly makes clustering easy! This extra (and totally optional) portion of the guide walks you through clustering your Elixir application.
There are 2 parts to getting clustering quickly setup on Fly.
- Installing and using
libcluster
- Scaling our application to multiple VMs
Adding libcluster
The widely adopted library libcluster helps here.
Libcluster supports multiple strategies for finding and connecting with other nodes. The strategy we'll use is DNSPoll
which was added in version 3.2.2 of libcluster
, so make sure you're using that version or newer.
After installing libcluster
, add it to the application like this:
defmodule HelloElixir.Application do
use Application
def start(_type, _args) do
topologies = Application.get_env(:libcluster, :topologies) || []
children = [
# ...
# setup for clustering
{Cluster.Supervisor, [topologies, [name: HelloElixir.ClusterSupervisor]]}
]
# ...
end
# ...
end
Our next step is to add the topologies
configuration to the file config/runtime.exs
.
app_name =
System.get_env("FLY_APP_NAME") ||
raise "FLY_APP_NAME not available"
config :libcluster,
debug: true,
topologies: [
fly6pn: [
strategy: Cluster.Strategy.DNSPoll,
config: [
polling_interval: 5_000,
query: "#{app_name}.internal",
node_basename: app_name
]
]
]
REMEMBER: Deploy your updated app so the clustering code is available, with fly deploy
.
This configures libcluster
to use the DNSPoll
strategy and 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.
- Scale our application to have multiple VMs in one region.
- Add a VM 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.
fly status
...
VMs
ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
f9014bf7 26 sea run running 1 total, 1 passing 0 1h8m ago
Scaling in a Single Region
Let's scale up to 2 VMs in our current region.
fly scale count 2
Count changed to 2
Checking on the status we can see what happened.
fly status
...
VMs
ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
eb4119d3 27 sea run running 1 total, 1 passing 0 39s ago
f9014bf7 27 sea run running 1 total, 1 passing 0 1h13m ago
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:
fly logs
...
app[eb4119d3] 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.
fly ssh console
/app/bin/hello_elixir remote
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 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 where your application is located. You can read about regions here and see the list of regions to choose from.
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 Parsippany, NJ (US). This puts an VM on both coasts of the US.
fly regions add ewr
Region Pool:
ewr
sea
Backup Region:
iad
lax
sjc
vin
Looking at the status right now shows that we're only in 1 region because our count is set to 1.
fly status
...
VMs
ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
cdf6c422 29 sea run running 1 total, 1 passing 0 58s ago
Let's add a 2nd VM and see it deploy to ewr
.
fly scale count 2
Count changed to 2
Now our status shows we have two VMs spread across 2 regions!
fly status
...
VMs
ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
0a8e6666 30 ewr run running 1 total, 1 passing 0 16s ago
cdf6c422 30 sea run running 1 total, 1 passing 0 6m47s ago
Let's ensure they are clustered together.
fly ssh console
/app/bin/hello_elixir remote
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!
Troubleshooting
Some problems are harder to diagnose because they deal with Elixir releases 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 inconfig/runtime.exs
, which is not used locally. Carefully review it. - Run
fly logs
to check server logs.