Deploy Elixir Phoenix to Render with Continuous Deployment

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.

  1. Create Elixir Phoenix Project
  2. Prepare App to Deploy on Render
  3. 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.

Add user to Elixir Phoenix app

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.

  1. Change config/prod.exs to no longer call import_config "prod.secret.exs" at the bottom.
  2. Rename config/prod.secret.exs environment file to config/runtime.exs.
  3. Change use Mix.Config to import Config inside the new config/runtime.exs file.
  4. Restrict your runtime configuration to the :prod environment
  5. 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.

Add user Elixir Releases

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.

Render Dashboard
Render Dashboard

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
Render Deploy Commands

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
Render Environment Variables
Render Web Service Environment

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.

Deploy Elixir Render
Deployment Progress on Render Dashboard

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.

Want To Know How To Deploy Phoenix Apps Using A single Command?

This brand-new FREE training reveals the most powerful new way to reduce your deployment time and skyrocket your productivity… and truly see your programming career explode off the charts!