We’re going to deploy Elixir and Phoenix to Render with Elixir releases, automated database migrations, and Continuous Deployment. Render was rated Best for Deploying Multiple Languages and Frameworks in our article covering the Best Cloud Hosting Providers for Elixir and Phoenix. It provides a streamlined process to deploy many languages and frameworks.
Overview
Here are the topics we’ll cover while deploying the project.
- Create Elixir Phoenix Project
- Prepare App to Deploy on Render
- Deploy Elixir App on Render with Continuous Deployment
Prerequisites
To follow this guide, make sure you have at least the following applications installed on your machine. In the brackets are the versions with which this guide was written.
- Elixir (1.12.3-otp-24)
- Erlang (OTP 24.1)
- Phoenix (1.5.13)
- Node.js with npm (15.14.0)
Make sure to install at least Elixir 1.9 since we are using releases. We recommend using asdf to manage Erlang and Elixir versions.
If you run into problems, the finished project is available on GitHub as StakNine HelloRender.
Create Elixir Phoenix Project
We are going to create a simple Phoenix project called HelloRender. Use the command line to update your Phoenix version and create a new Phoenix project.
Create new Phoenix app
mix archive.install hex phx_new 1.5.13
mix phx.new hello_render
Change into the project directory, create your database and start your app.
cd hello_render
mix ecto.create
mix phx.server
Now visit http://localhost:4000
and you should see the Welcome to Phoenix! page.
Add users
Now we want to add users to the project with the phx.gen html task. This allows us to verify the Postgres database is deployed properly for our project.
mix phx.gen.html Accounts User users name:string \
username:string:unique
Follow the directions to add the resource to the router.ex
file and migrate the database with mix ecto.migrate
.
Now start your server with mix phx.server
and visit http://localhost:4000/users
on your local machine to see the index of users. Add a new user to test out the database.
We’ll use this project to test our database is migrated and our app is deployed properly.
Let’s commit our changes and then configure for releases.
git init
git add .
git commit -m 'initialize repo and add users'
Next we’ll update the project to use releases.
Update for Elixir releases
Render uses Elixir mix releases in its deployment process. A release consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine and runtime. Deploying with mix releases instead of Mix unlocks many of Elixir’s features.
We are going to update our project to build and start releases on your local machine.
Runtime configuration
Update the project to use runtime configuration with the steps below. With runtime configuration, you are able to store environment variables in an external configuration system in an actual production setup. We are going to use config/runtime.exs
added in Elixir 1.11.
- Change
config/prod.exs
to no longer callimport_config "prod.secret.exs"
at the bottom. - Rename
config/prod.secret.exs
environment file toconfig/runtime.exs
. - Change
use Mix.Config
toimport Config
inside the newconfig/runtime.exs
file. - Restrict your runtime configuration to the
:prod
environment - Uncomment or add
server: true
.
# config/runtime.exs
import Config
if config_env() == :prod do
# other configuration from prod.secret.exs
config :hello_render, HelloRenderWeb.Endpoint, server: true
end
Ecto migrations and custom commands
You can’t run mix ecto.migrate
using releases because Mix is not available. We need to write a release commands file. Create a new file in your application, lib/hello_render/release.ex
, with the following:
defmodule HelloRender.Release do
@app :hello_render
def migrate do
ensure_started()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
ensure_started()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp ensure_started do
Application.ensure_all_started(:ssl)
end
defp repos do
Application.load(@app)
Application.fetch_env!(@app, :ecto_repos)
end
end
Once change from the Phoenix documentation is to add Application.ensure_all_started(:ssl)
to each function using ensure_started/0
. Since Render uses SSL to connect to the database, you need to start SSL before running your migrations. Read more on ElixirForum.
Deploy Your Elixir Release Locally
One you’ve completed those sections, you should be able to test your app and start building releases.
First set the following environment variables.
# generate a really long secret
mix phx.gen.secret
export SECRET_KEY_BASE=REALLY_LONG_SECRET
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/hello_render_dev
Then load dependencies to compile code and assets.
# Initial setup
mix deps.get --only prod
MIX_ENV=prod mix compile
# Compile assets
npm run deploy --prefix ./assets
mix phx.digest
Next drop the existing database and create a new database, so you can test the release command. Then run the mix command to build the release with the MIX_ENV=prod mix release build script.
mix ecto.drop
mix ecto.create
MIX_ENV=prod mix release
Migrate your database and start your application
_build/prod/rel/hello_render/bin/hello_render eval "HelloRender.Release.migrate"
_build/prod/rel/hello_render/bin/hello_render start
Now you should be able to visit http://localhost:4000/users
on your local machine to see the index of users. You can add a user again to test out the site is working properly.
Great! Let’s commit our changes and then prepare for deployment.
git add .
git commit -m "update for releases"
We’re ready to deploy our project to Render.
Prepare App to Deploy on Render
Now we’ll go through the process of configuring our app and deploying on Render. These steps are based on Renders’s guide to Deploy a Phoenix App with Mix Releases.
Create build script
Render uses a build script to run a series of commands to build your app on every push to your Git repo. Create a script called build.sh in the root of your repo.
#!/usr/bin/env bash
# exit on error
set -o errexit
# Initial setup
mix deps.get --only prod
MIX_ENV=prod mix compile
# Compile assets
npm install --prefix ./assets
npm run deploy --prefix ./assets
mix phx.digest
# Build the release and overwrite the existing release directory
MIX_ENV=prod mix release --overwrite
Make sure the script is executable before checking it into Git:
$ chmod a+x build.sh
Update config
Update config/prod.exs
with the following changes to the HelloRenderWeb.Endpoint
config. Render populates RENDER_EXTERNAL_HOSTNAME
for config/prod.exs
.
config :hello_render, HelloRenderWeb.Endpoint,
url: [host: System.get_env("RENDER_EXTERNAL_HOSTNAME") || "localhost", port: 80],
cache_static_manifest: "priv/static/cache_manifest.json"
Then update config/runtime.exs
to use ssl
for your database connection in production. You should be able to simply uncomment ssl: true
. Note: we added Application.ensure_all_started(:ssl)
to lib/hello_render/release.ex
to make sure SSL is started before our database migrations.
config :hello_render, HelloRender.Repo,
ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
At this point, we’ve completed updates to our application and are ready to test locally.
Build and Test Your Release Locally
Compile your release locally by running ./build.sh
.
Next, test your release by running the following command and navigating to http://localhost:4000
.
SECRET_KEY_BASE=`mix phx.gen.secret` _build/prod/rel/hello_render/bin/hello_render start
If everything looks good, push your changes to a new GitHub repo for the project. If you run into issues, check out this commit. Next we’ll set up continuous deployments through Render’s GitHub integration.
Deploy Elixir App on Render with Continuous Deployment
We’ll deploy for the first time and set up Continuous Deployment in one step. First we’ll go over the definitions of Continuous Integration / Continuous Deployment.
What is Continuous Integration / Continuous Deployment (CI/CD)?
According to Red Hat, the "CI" in CI/CD always refers to continuous integration, which is an automation process for developers. Successful CI means new code changes to an app are regularly built, tested, and merged to a shared repository.
The "CD" in CI/CD refers to continuous delivery and/or continuous deployment.
Continuous delivery usually means a developer’s changes to an application are automatically bug tested and uploaded to a repository (like GitHub), where they can then be deployed to a live production environment by the operations team.
Continuous deployment (the other possible "CD") can refer to automatically releasing a developer’s changes from the repository to production.
In our example, we only plan to set up Continuous Deployment. However, you can add a GitHub Actions workflow file to set up Continuous Integration using GitHub Actions.
Continuous Deployment with Render
We’ll both deploy for the first time and set up continuous deployment at the same time.
First, go to your Render Dashboard and click the button to generate a new Database.
Next, use the dashboard to spin up a new Web Service and give Render access to you GitHub repo. Use the following values when creating the Web Service:
- Environment:
Elixir
- Build Command:
./build.sh
- Start Command:
_build/prod/rel/hello_render/bin/hello_render eval "HelloRender.Release.migrate" && _build/prod/rel/hello_render/bin/hello_render start
Our Start Command
will first run migrations and then start the app on every deploy.
Next, under the Advanced section, add three new environment variables:
- SECRET_KEY_BASE: Generate using
mix phx.gen.secret
- DATABASE_URL: The internal connection string from your Database settings.
- ELIXIR_VERSION:
1.12.3
If you don’t specify an Erlang/OTP version, Render will automatically download a compatible Erlang runtime. Render provides a list of available Elixir and Erlang/OTP versions.
Now your web service will be live on your Render URL as soon as the build finishes.
Going forward, every push to your repo will automatically build your app and deploy it in production. If the build fails, Render will automatically stop the deploy process and the existing version of your app will keep running until the next successful deploy.
Conclusion
Congratulations! You went through the steps to deploy your Phoenix Elixir app to Render using Elixir releases and automated database migrations. You also set up Continuous Deployment for your project to automatically deploy to Render when you push commits to your default (e.g. main) branch on GitHub. Now that you’ve deployed to production, check out our article covering the best error monitoring services for Elixir so you can deploy with confidence and effectively manage errors in your system.