Deploy a simple Phoenix 1.3 api with Distillery 2.0 and basic_auth to Gigalixir
This write-up aims to explore one Elixir/Phoenix deployment option with Gigalixir. The goal is to help people check out Elixir/Phoenix and have something fun to experiment with quickly. By the end of this write-up you will have a simple Phoenix 1.3 JSON api deployed on Gigalixir ready to try out.
…skip ahead to the making stuff..here
Elixir/Phoenix deployment overview…..from really really far away, like space.
The nature of OTP is to use releases but Elixir/Phoenix were designed with strength of tooling and ease of use in mind. You can cut a build and deploy with Mix on say Heroku pretty easily. However this involves deploying your source code, hardware to be setup, loss of OTP features mainly hot code swapping some also consider this an addition attack vector.
The other option is to cut a build with Distillery which generates a release or tarball file that includes ERTS(erlang runtime system). This makes your build portable and ready for Docker containers or AWS. This allows you to be able to perform hot code swaps and other OTP features but once you take Mix out of it, running migrations becomes an issue. As well as splitting compile time and runtime ENV_VARS things are rapidly evolving in the deployment landscape.
Recommended reading if you want to know more, but not required to follow along.
Announcing Distillery 2.0 — blog post from bitwalker
*Distillery 2.0 introduces config_providers but still supports REPLACE_OS_VARS which is the method we will be using to handle ENV_VARS.
Assumptions
This write-up is intended to be a quick up and running guide, it makes the assumption you have poked around the Elixir/Phoenix stack before and have some basic programming experience. For more information please visit the docs for Gigalixir and Distillery.
Tools we are going to use
What is Gigalixir?
“Gigalixir is a fully-featured, production-stable platform-as-a-service built just for Elixir that saves you money and unlocks the full power of Elixir and Phoenix without forcing you to build production infrastructure or deal with maintenance and operations.”
For more information, see https://gigalixir.com.
I would also recommend reading — How does Gigalixir compare to Heroku?
What is Distillery?
“Distillery is a tool for packaging Elixir applications for deployment using OTP releases. In a nutshell, Distillery produces an artifact, a tarball, which contains your application and everything needed to run it. This artifact also contains scripts which allow you to run the application in three different modes (console, foreground, and daemonized), as well as a variety of utility commands, such as remote_console
which provides an easy way to connect an IEx session to your running application. Releases are more than just a way to package your application though, and are a core part of Erlang’s design, which we inherit in Elixir.”
For more information, see https://hexdocs.pm/distillery/home.html.
What we are going to do
Let’s make an api that represents a lunchbox that holds food. The lunchbox will be our context that handles our food items. The food will have a name:string
and have a status:string
to keep it simple. We will add in basic http auth with testing and deploy.
- make a basic JSON API with Phoenix 1.3
- add a schema and migration
- add basic_auth
- fix tests for auth
- run and test locally with postman
- sign up for Gigalixir free account
- login and create Gigalixir app
- create Gigalixir database
- prep app for Gigalixir
- build and release
- deploy
- run migrations
- test deployed api with postman
Let’s get going
make a basic JSON API with Phoenix 1.3
- run
$ mix phx.new --no-brunch --no-html lunchbox_api
to create a new project. - then
$ cd lunchbox_api/
$ mix deps.get
$ mix ecto.create
$ mix phx.server
- your expectation is to see your server running like this
- don’t forget to run
$ git init
on your new project and make an initial commit.
add a schema and migration
- run
$ mix phx.gen.json Lunchbox Food foods name:string status:string
- open up router.ex and add
resources “/foods”, FoodController, except: [:new, :edit]
to your api route
- run
$ mix phx.routes
to make sure your path helpers are there - run
$ mix ecto.migrate
to run the new foods migration. - run
$ mix phx.server
to see if your api is running
Your expectation is to visit localhost:4000/api/v1/foods
in your browser and see the output of {“data”:[]}
add basic_auth
- add basic auth to your deps in mix.exs
- run
$ mix deps.get
- add basic_auth plug to the top of food_controller.ex
- add config for basic_auth to config/test.exs
- you can also use .env instead of hardcoding with something like…
username: System.get_env(“BASIC_AUTH_USERNAME”)
password: System.get_env(“BASIC_AUTH_PASSWORD”)
fix tests for auth
- now we have basic_auth locking down our food_controller.ex but if you run
$ mix test
you will see adding auth has broken some tests. Let’s fix those! - next we need to setup auth for tests and take care of adding auth to the conn, add the following code to your food_controller_test.exs
- if you run
$ mix test
you will notice some tests are still failing, we now need to utilize our recycle conn func to get auth headers back on the conn for more reading see. Phoenix Docs for ConnTest - your final food_test_controller.exs file should look like this and all tests should be passing now.
- note adding auth to the conn only makes some tests pass, to make all of them pass we utilize the conn func
recycle/1
run and test locally with postman
- time to check everything out in dev
- add config for basic_auth to config/dev.exs
- you can also use .env instead of hardcoding with something like…
username: System.get_env(“BASIC_AUTH_USERNAME”)
password: System.get_env(“BASIC_AUTH_PASSWORD”)
- fire up your local server
$mix phx.server
and postman - send a get request to
http://localhost:4000/api/v1/foods
,don’t forget to set auth headers! - your expectation is to see a response of
{“data”:[]}
just like before
- now let’s test storing a food, make sure to set the body in postman to `raw` and change `text` to `JSON (application/json)`
{
"food": {
"name": "cheese",
"status": "really old"
}
}
- your expectation is to see your new food item returned in the response
{“data”:{“status”:”really old”,”name”:”cheese”,”id”:1}}
- success!! we are almost there, whew!
sign up for Gigalixir free account
login and create Gigalixir app
- check pip version
pip -V
if you don’t have pip install with$sudo easy_install pip
- open up terminal and install the Gigalixir CLI
$sudo pip install gigalixir — ignore-installed six
- login to Gigalixir with
$ gigalixir login
- run
$ gigalixir account
to verify your new login
- create a new app
$ APP_NAME=$(gigalixir create)
and verify with$gigalixir apps
- check the remote
$ gigalixir remote -v
create Gigalixir database
- run
$ gigalixir config
and you will notice it is empty
- create a new database,
$ gigalixir pg:create — free
- verify with
$ gigalixir pg
- now run
$ gigalixir config
you will see your new db url has already been added
prep app for Gigalixir
- add Distillery to your deps in mix.exs,
{:distillery, “~> 2.0”}
- run
$mix deps.get
- delete the file
config/prod.secrets.exs
- from
config/prod.exs
remove the lineimport_config “prod.secret.exs”
- adjust your
config/prod.exs
to look like this - don’t forget to add the config for basic_auth in prod
- create a file named
.buildpacks
in your api root and add the following
https://github.com/gigalixir/gigalixir-buildpack-clean-cache.git
https://github.com/HashNuke/heroku-buildpack-elixir
https://github.com/gigalixir/gigalixir-buildpack-distillery.git
build and release
- if you have not been committing with git make sure you do that now.
- run
$ mix release.init
* we are skipping$ mix phx.digest
since we don’t have static assets but you will need to in order to deploy a full app. - commit changes and run
$ mix test
to make sure nothing is broken - run
$ MIX_ENV=prod mix release — env=prod
to make a release - your expectation is to see this in the output
deploy
- have you made a git commit lately?
- run
$ git push gigalixir master
run migrations
- Run
$ gigalixir add_ssh_key “$(cat ~/.ssh/id_rsa.pub)”
to add SSH keys - run
$ gigalixir ps:migrate
test deployed api with postman
- now we need to add some VARS for basic auth to Gigalixir
- run
$ gigalixir config:set BASIC_AUTH_USERNAME=”someUserName”
- and
$ gigalixir config:set BASIC_AUTH_PASSWORD=”superSecretPassword”
- run $ gigalixir config again and now you should see your VARS have been added
- now test with postman,
https://your-app-name.gigalixirapp.com/api/v1/foods
and you will see we are not authorized
- add auth to postman and try again
- success! now let’s make some food
Summary
…and that is it! now you have a working api deployed to Gigalixir with some simple auth to try out. You can even visit your api on the web and see it will ask for auth to see your new cheese items. I hope this helps someone out there get up and running with Elixir/Phoenix and Gigalixir. Let me know what you think/ how it goes!