19 July 2015

The following will first show off using Phoenix with exrm and then try some ideas to improve usage.

Assumes a freshly generated Phoenix app called hello_phoenix & the current working dir is the project’s root dir.

The setup

  • Add exrm as dependency in mix.exs

  • Fetch & compile deps

    $ mix deps.get
    $ mix deps.compile
    

Generating & running apps via releases

As soon as you generate a Phoenix or any mix app, your app’s version in mix.exs would be 0.0.1. To generate a release run the following:

$ mix release

You can now start the app with the following:

$ rel/hello_phoenix/bin/hello_phoenix start

To check if your application is running, ping your application and it should return pong.

$ rel/hello_phoenix/bin/hello_phoenix ping
pong

Apart from start & ping You can run other common tasks like:

  • stop - stops the running app
  • console - to start the app with an IEx shell
  • remote_console - to connect to an already running app and start a shell

and a few more.

Serve the web app

To start a phoenix app normally, you would run mix phoenix.server.

An exrm release does not come with any mix tasks. This means, like before, you cannot start the Phoenix server using the mix phoenix.server mix task.

To workaround this, you can instruct Phoenix to start the server by default when the OTP app starts. This can be done by setting the server option to true in your environment specific config. We’ll assume dev env right now. So our config file would be config/dev.exs.

Change the following:

config :hello_phoenix, HelloPhoenix.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  cache_static_lookup: false,
  watchers: [node: ["node_modules/brunch/bin/brunch", "watch"]]

to look like below:

config :hello_phoenix, HelloPhoenix.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  cache_static_lookup: false,
  watchers: [node: ["node_modules/brunch/bin/brunch", "watch"]],
  server: true

Notice the extra server: true option in the end?

Now lets blow up the release and create a new one. Ensure your release is stopped and then run the following to destroy everything about releases in your app.

$ mix release.clean --implode --no-confirm

Create another release and start the release. Your app should now be accessible at localhost:4000 as usual.

Hot code-reloading a running app

Instead of stopping & starting apps everytime you want to deploy. Your flow would be the following if you used hot code-reloading.

Whenever the app is updated, this would be the workflow:

  1. change the app’s version in the project function mix.exs
  2. create a release
  3. then run: mix release

Assuming there’s a release already running for version 0.0.1 of the app & the next version of the app is 0.0.2, as mentioned in mix.exs, you would do the following:

$ SERVER=1 mix release
$ rel/hello_phoenix/bin/hello_phoenix upgrade "0.0.2"

There are some commands to be run before actually upgrading the release (moving it to a tmp dir, etc). But I’ve kept it simple for now, more details are available in the exrm docs page on releases.

Improvements for convenience

When release is running, cannot run any other mix task that runs app.start task

Doing the above would mean that any task that runs app.start will error out. Running app.start would mean that the app is trying to use the same port, which a live release is using. One of those tasks is ecto.migrate (atleast for now. I’ve read that it’ll change soon with Ecto.Migrator). So migrating the database when a release is running, is going to be nope-nope atleast for now.

Improvise: Let the release start serving the web app automatically. But when mix tasks are run, don’t start the web app.

The phoenix.server task sets the serve_endpoints: true. This is an alternative to setting server: true like we did earlier. This time, we’ll use serve_endpoints instead. This is independent of app’s name and copyable across apps :)

Add this to the environment config (whichever env is being used):

if System.get_env('SERVER') do
  config :phoenix, :serve_endpoints, true
end

Now implode your release and start from scratch. To create a release use SERVER=1 mix release. Starting the release will serve the web app too.

To run normal mix tasks, run them WITHOUT the SERVER=1 env var. The web app won’t be started :)

For small hobby projects, upgrading version in mix.exs frequently is repetitive

Improvise: Use git commit SHAs as versions

Using only a truncated git commit SHA like 3c4dc19 for a version is fine too. But let’s follow semver. Semver allows using custom labels in the version. Having a version like 1.4.1-3c4dc19 is fine by semver. This way I can upgrade actual version numbers only when it rains.

So in our mix.exs we’ll change this:

def project do
  [app: :hello_phoenix,
   version: "1.4.1",
   elixir: "~> 1.0",
   ...

and fetch the git commit SHA like this:

def project do
  {result, _exit_code} = System.cmd("git", ["rev-parse", "HEAD"])

  # We'll truncate the commit SHA to 7 chars. Feel free to change
  git_sha = String.slice(result, 0, 7)

  [app: :hello_phoenix,
   version: "1.4.1-#{git_sha}",
   elixir: "~> 1.0",
   ...

Now to upgrade your release, do:

$ rel/hello_phoenix/bin/hello_phoenix upgrade "1.4.1-3c4dc19"

Feels awkward to type that version manually, but when deploying apps to servers, an automated tool is most likely going to automate this.

If you are looking for a quick & easy way to deploy your Elixir apps to servers, checkout ansible-elixir-stack. It is built on top of Ansible. Tiny details like the ones mentioned here are taken care of for you.