Learn how to deploy your Elixir Phoenix app to Gigalixir using releases. Gigalixir lets you take advantage of many Elixir features not available on other cloud hosting platforms. For this reason, Gigalixir was our Editor’s Choice from our Best Cloud Hosting Providers for Elixir Phoenix article. In this guide, you’ll learn how to prepare your app for releases, create your Gigalixir app, deploy, and automate migrations.
Overview
Here are the topics we’ll cover while deploying the project.
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 HelloGigalixir.
Why Gigalixir?
Gigalixir allows you to use all the features Elixir has to offer like distributed clustering, hot upgrades, remote console, and production observer.
Phoenix can handle over two million simultaneous websocket connections on a single node and it’s great for massively real-time apps. Gigalixir doesn’t limit your connection counts or duration, so you can leverage the full power of Phoenix with websockets.
Finally, GenServer state, ETS, and cachex make it possible to create ridiculously high performance, fault-tolerant apps but daily restarts will get in your way. Distributed Phoenix channels don’t need Redis if you have your nodes clustered.
Many other cloud hosting platform limit your ability to use these features. Gigalixir is a great option to use the full capability of Elixir and Phoenix.
Create a New Phoenix App
We are going to create a simple Phoenix project called HelloGigalixir. Use the command line to update your Phoenix version and create a new Phoenix project.
mix archive.install hex phx_new 1.5.13
mix phx.new hello_gigalixir
Change into the project directory, create your database and start your app.
cd hello_gigalixir
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.
mix phx.gen.html Accounts User users name:string \
username:string:unique
Follow the directions to add the resource to the lib/docker_phx_web/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 make the initial commit to your git repository and then configure for releases.
git init
git add .
git commit -m 'initialize repo and add users'
Configure for Elixir Releases
Using Elixir releases to deploy on Gigalixir unlocks many of Elixir’s features, including remote observer. A release consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine and runtime.
Configure Environment Variables
We are going to update our project to use releases. First set the following.
# 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_gigalixir_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
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
inside the new config/runtime.exs file toimport Config
. - 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
config :hello_gigalixir, HelloGigalixirWeb.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_gigalixir/release.ex, with the following:
defmodule HelloGigalixir.Release do
@app :hello_gigalixir
def migrate do
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.load(@app)
Application.fetch_env!(@app, :ecto_repos)
end
end
Deploy Your Elixir Release Locally
Once you’ve completed those sections, you should be able to test your app and start building releases.
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_gigalixir/bin/hello_gigalixir eval "HelloGigalixir.Release.migrate"
_build/prod/rel/hello_gigalixir/bin/hello_gigalixir 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 to your git repository and then prepare for deployment.
git add .
git commit -m "update for releases"
Deploy to Gigalixir
We’re ready to deploy our app. First we will add deployment configuration. Then we’ll create and deploy our app using the Gigalixir CLI. Finally, we will try out remote console and observer.
Add buildpacks
Now that we’ve used releases to deploy to our local machine, we’re ready to deploy to our remote server. First, we need to create a .buildpack
file since we are using runtime configuration file. Runtime configuration can be used with either mix or releases, so you need to tell Gigalixir how you want to start your application based on the last line in the file.
#.buildpack file
https://github.com/HashNuke/heroku-buildpack-elixir
https://github.com/gjaldon/heroku-buildpack-phoenix-static
https://github.com/gigalixir/gigalixir-buildpack-releases.git
Then let’s make sure we add buildpack config to use the same versions of Elixir, Erlang, and Node in both development and production.
echo "elixir_version=1.12.3" > elixir_buildpack.config
echo "erlang_version=24.1" >> elixir_buildpack.config
echo "node_version=15.14.0" > phoenix_static_buildpack.config
Update runtime config
Now we need to make some additional updates to the config for Gigalixir.
- Set your pool size to 2 since we will be using a free tier database
- Update
HelloGigalixirWeb.Endpoint
endpoint and repo config
# changes to config.runtime.exs
config :hello_gigalixir, HelloGigalixir.Repo,
# ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "2")
config :hello_gigalixir, HelloGigalixirWeb.Endpoint,
server: true,
http: [port: {:system, "PORT"}],
url: [host: System.get_env("APP_NAME") <> ".gigalixirapp.com", port: 443]
Procfile for migrations on startup
To automatically migrate our database on startups and updates using releases, we need to create a custom Procfile and overlay it into our release tarball. To do this create a file rel/overlays/Procfile
with the following.
web: /app/bin/$GIGALIXIR_APP_NAME eval "HelloGigalixir.Release.migrate" && /app/bin/$GIGALIXIR_APP_NAME $GIGALIXIR_COMMAND
Phoenix v1.6 update
If you’re using Phoenix v1.6, it uses esbuild to compile your assets but Gigalixir images come with npm, so we will configure npm directly to deploy our assets. Add a assets/package.json file if you don’t have any with the following:
{
"scripts": {
"deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild"
}
}
Now we’re ready to commit our changes to your git repository and deploy.
git add .
git commit -m 'update for deployment'
Create app and deploy
We’ve configured your Phoenix project for releases and we’re ready to deploy to Gigalixir. If you haven’t already, install the gigalixir CLI and sign up for an Gigalixir account. We are using macOS. If your are using another OS, check out the install options for Windows and Linux.
brew tap gigalixir/brew && brew install gigalixir
gigalixir signup
gigalixir login
Create a Gigalixir app. You will get your app name in the output and have a git remote set for gigalixir.
APP_NAME=$(gigalixir create)
Verify that the app was created, by running gigalixir apps
gigalixir apps
The following command will provision a free database for you and set your DATABASE_URL environment variable appropriately.
gigalixir pg:create --free
Verify your database is up by running
gigalixir pg
Once the database is created, verify your configuration includes a DATABASE_URL
with
gigalixir config
Now your are ready to push your code which will initiate the deployment of your production app. You will get the link including your app name in the output.
git push gigalixir main
Gigalixir will deploy your app.
Wait a minute or two for the app to pass health checks. Then you should notice Gigalixir running migrations on startup using the script you added to rel/overlays
. You can check the status with
gigalixir ps
Now visit https://app_name.gigalixirapp.com/users
and add a user to test your app running in production is deployed properly.
Using the Gigalixir Dashboard
Gigalixir provides a dashboard for your app running in production to monitor its health and view logs.
Conclusion
Congratulations! You’ve deployed your Elixir Phoenix project to Gigalixir using releases. In future posts, we plan to cover continuous deployment, custom domains, ssh access, remote observer, and remote console.