# Preparing a Phoenix 1.6 app 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](https://blog.miguelcoba.com/creating-a-phoenix-16-application-with-asdf) 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:  

```bash
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:  

```bash
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:  

```bash
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.  

```bash
MIX_ENV=prod mix phx.server
```

If you go to [http://localhost:4001/](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.

![Phoenix App running in prod mode](https://cdn.hashnode.com/res/hashnode/image/upload/v1635361326294/BuOZUxzMY.png)

##  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"  

```elixir
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:  

```bash
MIX_ENV=prod mix release
```

##  Run the release

We can now run the release executable generated by the mix release task:  

```bash
_build/prod/rel/saturn/bin/saturn start
```

If you go again to [http://localhost:4001/](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:  

```Elixir
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:  

```bash
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:  

```bash
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:  

```bash
export DATABASE_URL=ecto://postgres:postgres@localhost/saturn_prod
```

And now you can run the migrations:  

```bash
_build/prod/rel/saturn/bin/saturn eval "Saturn.Release.migrate"
```

and you should see something like this:  

```bash
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:  

```bash
_build/prod/rel/saturn/bin/saturn start
```

Go again to [http://localhost:4001](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](https://gigalixir.com/) in a future post.

Cheers

## About

I'm [Miguel Cobá](https://miguelcoba.com). I write about Elixir, Elm, Software Development, and eBook writing.

- Follow me on [Twitter](https://twitter.com/MiguelCoba_)
- Subscribe to my [newsletter](https://newsletter.miguelcoba.com)
- Read all my articles on my [blog](https://blog.miguelcoba.com)
- Get my books:
    - [100 Elixir Tips](https://store.miguelcoba.com/l/100elixirtips) [FREE]
    - [Deploying Elixir](https://store.miguelcoba.com/l/deployingelixir) [FREE]
    - [Deploying Elixir: Advanced Topics](https://store.miguelcoba.com/l/advancedtopics)
    - [eBook Writing Workflow for Developers](https://store.miguelcoba.com/l/ebookwriting)

Photo by [Gautier Salles](https://unsplash.com/@yamnez?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/phoenix?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
  
