Subscribe to access all episodes. View plans →

#112: Deploying with Gigalixir (Revised)

Published April 3, 2020

Elixir 1.10

Phoenix 1.4.4

Giglalixir


In this episode we’ll see how easy it is to deploy an Elixir application with Gigalixir.

Any language that has a buildpack can run on Gigalixir, but it was originally built for Elixir and supports the great features of Elixir like hot upgrades, distributed clustering, and a production observer. And if you’re using the free tier on Gigalixir there’s no daily restart like on the Heroku free tier. This allows for using things like Erlang Term Storage, or ETS, for more than just holding ephemeral state.

Alright, let’s get started by creating a new app. We’ll create a new Elixir Phoenix application named teacher.

$ mix phx.new teacher
...

Then let’s move into the teacher directory and create our app’s database with mix ecto.create.

$ mix ecto.create
The database for Teacher.Repo has been created

Now let’s test that everything is working locally. We’ll start up our server.

$ mix phx.server
...

Then if we open our browser and go to localhost:4000. Great - our app is running locally. Now that we know it’s working locally, let’s get it ready to be deployed. Gigalixir supports 3 different ways to deploy your Elixir application. You can use Mix, Distillery, or Elixir Releases. For this episode let’s use Distillery. Let’s go to Hex and get the distillery package. Let’s open our application’s Mixfile. And add distillery to our list of dependencies.

mix.exs

...
defp deps do
[
...
{:distillery, "~> 2.1", runtime: false},
...
]
end
...

Now we can go to the command line and install distillery.

$ mix deps.get
...
distillery 2.1.1

Once installed, let’s run mix distillery.init which will create a rel directory that has our release configuration file.

$ mix distillery.init
...
An example config file has been placed in rel/config.exs, review it,
make edits as needed/desired, and then run `mix distillery.release` to build the release

Let’s open that config file in our project. We’ll use the defaults for this example, but you’ll want to review this in order to configure things like your release version and applications included in the release.

Since the version of Phoenix we’re using is greater than 1.4.4 the prod.secret.exs has been updated to use environment variables for configuration. Let’s make a few changes to ours. We’ll update our Teacher.Repo config, I’ll go ahead and paste ours changes. You can see that we’re using Postgres. And the database URL is also set by Gigalixir when we create our database, so that will work as-is, but we will need to make sure we set our POOL_SIZE environment variable. I’ll leave ssl: true commented out for now so that we can check that our distillery release works locally.

config/prod.secret.exs

...
config :teacher, Teacher.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "",
  # ssl: true,
  url: database_url,
  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
...

Then let’s open our prod.exs and we’ll make some updates to our Endpoint configuration setting load_from_system_env: true, our PORT, we’ll also set server: true, and our secret_key_base. Then we’ll need to update our URL host to what we expect for Gigalixir.

config/prod.exs

...
config :teacher, TeacherWeb.Endpoint,
  load_from_system_env: true,
  http: [port: {:system, "PORT"}], 
  server: true,
  secret_key_base: "${SECRET_KEY_BASE}",
  url: [host: "${APP_NAME}.gigalixirapp.com", port: 443],
  cache_static_manifest: "priv/static/cache_manifest.json"
...

Now before we deploy, let’s check that our changes work by building Distillery release locally. Let’s go back to the command line and we’ll move into our assets directory, then we can run npm run deploy. Once that finishes let’s go back to our project’s root and run mix phx.digest to compress our static files.

$ cd assets
$ npm run deploy
$ cd ../
$ mix phx.digest
Check your digested files at "priv/static"

Now to build a release our application will expect some environment variables so let’s go ahead and export those here. Our SECRET_KEY_BASE the DATABASE_URL, APP_NAME, MY_HOSTNAME, MY_COOKIE, REPLACE_OS_VARS, with that we can build our release locally. Great - the release was built successfully. Now let’s clear the screen and then let’s start our release in the foreground, specifying PORT=4000.

Now we should be able to access it at localhost:4000 - let’s check it out. And great, everything worked. Now that we know our release works locally we can deploy it.

$ export SECRET_KEY_BASE="$(mix phx.gen.secret)"
$ export DATABASE_URL="postgresql://postgres:postgres@localhost:5432/teacher_dev" 
$ export APP_NAME=teacher
$ export MY_HOSTNAME=example.com
$ export MY_COOKIE=secret
$ export REPLACE_OS_VARS=true
$ MIX_ENV=prod mix distillery.release --env=prod
$ MIX_ENV=prod MY_NODE_NAME=teacher@127.0.0.1 PORT=4000 _build/prod/rel/teacher/bin/teacher foreground
[info] Running TeacherWeb.Endpoint with cowboy 2.7.0 at :::4000 (http)

Let’s go back to our prod.secret.exs and before we left ssl: true commented out in our Repo config, let’s uncomment it.

config/prod.secret.exs

...
ssl: true,
...

Since Gigalixir uses git we need to create a new Git repo for our app. We’ll go to the command line and create a new Git repo with git init, then let’s add our project and commit our changes.

$ git init
$ git add .
$ git commit -m "Deploy to Gigalixir"

We can deploy our project from the command line using Gigalixir’s command line interface or CLI. The CLI requires pip in order to install, so you’ll need to have it installed first. With pip installed we can install the Gigalixir CLI. Once installed we can login to our account. If don’t have a Gigalixir account, pause this video and signup at gigalixr.com - or you can using CLI.

$ pip install gigalixir
Successfully installed gigalixir-1.1.7
$ gigalixir login
Email: hello@elixircasts.io
Logged in as hello@elixircasts.io.

Once we’re logged in we’ll create a new app in Gigalixir. And once it’s created, Gigalixir returns a unique name for our app, our app name here is lone-wiry-cuttlefish. We can double-check that it was created by listing our apps. Great, we see it along with some information about it.

We see cloud “gcp” - this is because by default Gigalixir runs our app on the Google Cloud Platform. There’s also the option to use AWS, which you can set when you create your app. There’s also the region, number of replicas - ours is zero right now because we haven’t deployed anything, the size, stack, our app’s name, and version.

$ gigalixir create
lone-wiry-cuttlefish
$ gigalixir apps
[
  {
    "cloud": "gcp",
    "region": "v2018-us-central1",
    "replicas": 0,
    "size": 0.5,
    "stack": "gigalixir-18",
    "unique_name": "lone-wiry-cuttlefish",
    "version": 2
  }
]

Since Phoenix 1.4 requires a DATABASE_URL environment variable at compile time we’ll need to create our database before we deploy, which we can do with another gigalixir command pg:create and for this example we’ll just create free database, which isn’t suitable for a production app, but will work for our example. Because this is a free-tier database, we’ll want to set the POOL_SIZE environment variable to “2”, so let’s set that with gigalixir config:set POOL_SIZE=2.

$ gigalixir pg:create --free
...
$ gigalixir config:set POOL_SIZE=2
"setting POOL_SIZE, and restarting. Run `gigalixir ps` to check status."

One last step before we deploy and that’s to ensure that our deployed application is using the same version of Elixir we’re using here.

To do that let’s create a new file named elixir_buildpack.config. Then we can set the version of Elixir we want to use, the version of Erlang, let’s also set always_rebuild=true - this rebuilds our app from scratch on every deploy.

elixir_buildpack.config

elixir_version=1.10.2
erlang_version=22.2
always_rebuild=true

And since we’re using Phoenix let’s ensure same versions of node and npm are used. We’ll create a file named phoenix_static_buildpack.config and in it set our version of Node, NPM, and set clean_cache=true - this will clean out the cache contents from previous deploys.

phoenix_static_buildpack.config

node_version=12.16.1
npm_version=6.14.3
clean_cache=true

With that let’s commit our changes.

$ git add .
$ git commit --m "sets elixir/erlang/node version"

Alright, now lets deploy our app. When we ran gigalixir apps:create Gigalixir added a git remote named “gigalixir”. We’ll run git push gigalixir master - this pushes our code to Gigalixir and triggers a deploy. Once finished, it will take a minute for our new instance to roll out and pass health checks.

$ git push gigalixir master
...

Let’s copy the URL that was printed in our logs - .gigalixirapp.com - and if we open it in our browser - great, we see our app is deployed!

Now that we have a deployed app, let’s make some changes to it and give our site a simple blog. Let’s go to the command line and run the Phoenix HTML generator to create our blog with a posts table and we’ll give our posts a title and a body. Then we’ll migrate our database.

$ mix phx.gen.html Blog Post posts title body
* creating lib/teacher_web/controllers/post_controller.ex
* creating lib/teacher_web/templates/post/edit.html.eex
* creating lib/teacher_web/templates/post/form.html.eex
* creating lib/teacher_web/templates/post/index.html.eex
* creating lib/teacher_web/templates/post/new.html.eex
* creating lib/teacher_web/templates/post/show.html.eex
* creating lib/teacher_web/views/post_view.ex
* creating test/teacher_web/controllers/post_controller_test.exs
* creating lib/teacher/blog/post.ex
* creating priv/repo/migrations/{timestamp}_create_posts.exs
* creating lib/teacher/blog.ex
* injecting lib/teacher/blog.ex
* creating test/teacher/blog_test.exs
* injecting test/teacher/blog_test.exs

Add the resource to your browser scope in lib/teacher_web/router.ex:

    resources "/posts", PostController


Remember to update your repository by running migrations:

    $ mix ecto.migrate

$ mix ecto.migrate

Then let’s open our router and add our new resource.

lib/teacher_web/router.ex

...
scope "/", TeacherWeb do
  pipe_through :browser

  resources "/posts", PostController
  get "/", PageController, :index

end
...

Let’s make it easy to access our blog from our UI, so let’s open our app layout and add a link to our blog.

Template path: lib/teacher_web/templates/layout/app.html.eex

<li>
  <%= link "Blog", to: Routes.post_path(@conn, :index) %>
</li>

Before we deploy these changes, let’s make sure they’re working locally. We’ll start our server.

$ mix phx.server
...

Then we’ll go back to our browser and we see our blog is working. Now to deploy our blog we’ll commit our changes.

$ git add .
$ git commit -m "adds blog"

Then let’s redeploy our app to Gigalixir.

$ git push gigalixir master
...

Once the deploy finishes we’ll go back to our application. If we refresh the page we see our blog link, but when we go to it we get an error. This is because we need to migrate our database.

Let’s go back to the command line. And we can run the migrations on our app with gigalixir ps:migrate, however, when we run our database migrations we get a message that we don’t have any ssh keys added. Let’s add ours with gigalixir account:ssh_keys:add. Then with our SSH key added we should be able to run our database migration.

$ gigalixir ps:migrate
You don't have any ssh keys yet. See `gigalixir account:ssh_keys:add --help`
$ gigalixir account:ssh_keys:add "$(cat ~/.ssh/id_rsa.pub)"
Please allow a few minutes for the SSH key to propagate to your run containers.
$ gigalixir ps:migrate
Migration succeeded.

Now if we go back to the browser - and refresh the page our blog loads. We are also able to create blog posts.

Great, with our app is deployed, let’s check out some other features of Gigalixir. First let’s see how to start a remote console. We’ll use the task $ gigalixir ps:remote_console. Once it starts up we can execute Elixir code. Earlier we set the POOL_SIZE environment variable to 2. Let’s test that that worked. We’ll call System.get_env passing POOL_SIZE - and great "2" was returned.

$ gigalixir ps:remote_console
> System.get_env("POOL_SIZE")
"2"

Finally, let’s look at one last feature of Gigalixir, the remote observer. We can start the observer with $ gigalixir ps:observer. Once it opens we’ll click on “Nodes”, and we see one with our app name is displyed, let’s connect to it. And great - we’re connected to our production node.

We can now view data about it, like our “Load Charts”. And our applications. If we click on our teacher application, we can see all our different processes running.

$ gigalixir ps:observer
============
Instructions
============
In the 'Node' menu, click 'Connect Node'
enter: 
and press OK

As you can see there’s a lot to love about Gigalixir.

While we covered a lot of the great features of Gigalixir, one thing we didn’t go over is node clustering.

Gigalixir supports node clustering and once it’s working things like distributed Phoenix channels will just work without any additional setup.

© 2024 HEXMONSTER LLC