Preparing a Phoenix 1.6 app for deployment with Elixir Releases
Prepare a Phoenix 1.6 application for deployment with Elixir Releases
Updates
- 20211009 added "Migrations support" section
I'm going to prepare a Phoenix application for deployment.
Prerequisites
Ensure to have your Phoenix 1.6 app running locally.
Runtime configuration
When deploying to production is better to inject runtime info to the application when it starts instead of having that info hardcoded in the source code. We pass info to the app to affect the way it works depending on the environment we are deploying the app to (e.g. staging, production).
Elixir 1.11 has introduced a way to inject this runtime info easily with the config/runtime.exs
config file. If you open that file you'll see that it obtains some values from environment variables. The default environment variables to configure are POOL_SIZE
, PORT
, DATABASE_URL
and SECRET_KEY_BASE
. We need to specify a value for those envvars if we want our deployment to work correctly.
For now we are going to test it locally, in our laptop. In a deployment service, like Gigalixir or Fly.io, those envvars are going to be provided when the app starts. We are going to do that manually here:
export POOL_SIZE=2
export PORT=4001
export DATABASE_URL=ecto://postgres:postgres@localhost/saturn_dev
export SECRET_KEY_BASE=$(mix phx.gen.secret)
I have my database named locally saturn_dev and the user and password the ones shown. You can see your own connection parameters in config/dev.exs
Build in production mode
Compile elixir code
We can now get the production dependencies:
mix deps.get --only prod
MIX_ENV=prod mix compile
Compile assets
If the project has JS, CSS or other assets you can also compile them with the esbuild wrapper that phoenix now uses:
MIX_ENV=prod mix assets.deploy
Test that the project starts in prod mode
By now, if you haven't closed the terminal, you'll have the previous envvars still defined. If you have closed the terminal, you need to set them again.
MIX_ENV=prod mix phx.server
If you go to http://localhost:4001/ you'll see the homepage of the app, but this time it is using the configuration that the config/runtime.exs
read from the terminal when it started instead of using the config/dev.exs
configuration. One thing you'll notice is that the LiveDashboard link is gone. This works only in dev mode.
Generate a release
We need to do an extra step before building the release using Elixir Releases. Open config/runtime.exs
and uncomment the following line, in the section titled "Using releases"
config :saturn, SaturnWeb.Endpoint, server: true
This direct the app to start the webserver when running the release executable. When we used mix phx.server
this was done for us. Now we need to explicitly enable it.
After saving those changes we can now generate the release:
MIX_ENV=prod mix release
Run the release
We can now run the release executable generated by the mix release task:
_build/prod/rel/saturn/bin/saturn start
If you go again to http://localhost:4001/ you'll see the app running, but this time from the self-contained bundle that the Elixir Releases generated for us.
Migrations support
There is one more thing before finishing. Right now we are using a database that was created by a mix ecto.create
command. But the release we just generated has no support for running mix in production. There is no mix
command anywhere inside the _build/prod/rel
folder. So how are we going to create the database and to run the phoenix migrations? Good question. We need a workaround that is embedded in the application itself.
Create a file in lib/saturn/release.ex
and put this content there:
defmodule Saturn.Release do
@app :saturn
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end
Save the file and regenerate the release:
MIX_ENV=prod mix release
Let's create a production database in our local postgres to simulate the production database in our production environment. Login to PostgreSQL locally create the database:
psql -U postgres -h localhost
psql (13.4)
Type "help" for help.
postgres=# CREATE DATABASE saturn_prod;
CREATE DATABASE
You need to change the DATABASE_URL
to point to the new database:
export DATABASE_URL=ecto://postgres:postgres@localhost/saturn_prod
And now you can run the migrations:
_build/prod/rel/saturn/bin/saturn eval "Saturn.Release.migrate"
and you should see something like this:
23:41:17.647 [info] Migrations already up
Now you can start the app and it will point to the saturn_prod
database we just created:
_build/prod/rel/saturn/bin/saturn start
Go again to http://localhost:4001 and the app will work normally, but now the database is fully migrated.
That's it.
You can put the contents of the _build/prod/rel/saturn
folder in your production server (as long as same architecture that the computer you used to assemble the release) and start it. You don't need anything else installed because this folder includes all the dependencies and binaries required to run the application. As long as you set the environment variables with correct values for production, this should work flawless.
You could do that manually, for example, by copying this folder to a DigitalOcean droplet or any other VPS provider, but there are better ways to do that.
I'll show you how to deploy to Gigalixir in a future post.
Cheers
About
I'm Miguel Cobá. I write about Elixir, Elm, Software Development, and eBook writing.
- Follow me on Twitter
- Subscribe to my newsletter
- Read all my articles on my blog
- Get my books:
Photo by Gautier Salles on Unsplash