Today we’re starting on a series of posts that delves into everyone’s favorite topic… DevOps 😱! Our goal will be to deploy a simple Phoenix application to AWS. We’ll be using the new Elixir 1.9 release task, along with Docker, and Terraform. This will be a two or three part series; today we’ll concentrate on creating a release and building a docker image. In subsequent posts we’ll deploy the docker image to AWS.
I find one of the challenges with DevOps is all the moving parts. Building the code; containerization; provisioning infrasture; continuous integration… it’s a lot! In this series we’re going to ignore the CI component altogether, but hopefully provide a decent jumping off point for everything else.
Let’s get at it! The first step is to create a simple application.
What we’ll build
Our application will be a trivial “warehouse” application that just contains a list of products.
Create the app
We’ll start from scratch with a new Phoenix application and add some scaffolding for the CRUD functionality.
Terminal
Choose Y
when asked to fetch and install dependencies, then change into the application directory and create the database.
Terminal
Our basic application is good to go, now let’s add the scaffolding.
Adding some scaffolding
Terminal
As per the mix
output, let’s update the routes…
/lib/aws_warehouse_web/router.ex …line 16
… and run the migrations.
Terminal
Our application is complete, let’s have a quick look:
Terminal
Navigating to http://localhost:4000/products we can see the products page and we are able to create a few products…
Nothing impressive, but it will be sufficient for our purposes. The next step is to create the release.
Create the release
We will be leaning heavily on this excellent article (https://akoutmos.com/post/multipart-docker-and-elixir-1.9-releases/) by Alex Koutmos to guide us through the creation of a release and a docker image. I’d highly recommend you give it a read. It explains the process much better than I could, and since there is already a great resource explaining releases / docker images, I won’t go into an in depth discussion here. I will point out where we deviate from Alex’s article, the main difference being we are creating an application with a database.
Our first step is to create the release files and configuration.
Terminal
Terminal
/config/release.exs
The main point of the release configuration is to specify items we want set at run time. Typically these are environment specific items. For instance, likely a different database user and password would be used on a QA versus a Production environment. Likewise, our SECRET_KEY_BASE
, DB_HOST
and DB_INSTANCE
values are all things that will be environment specific.
There are a few more configuration items to take care of.
We can get rid of config/prod.secret.exs
as the secrets.exs
items have all been included in the releases.exs
configuration we created.
Terminal
We need to uncomment the server: true
line in config/prod.exs
.
/config/prod.exs …line 58
And we also need to get rid of the prod.secret
import on line 65 as we no longer have a prod.secret.exs
file.
/config/prod.exs …line 65
With that, we are ready to build the release.
Terminal
Terminal
Now let’s test the release by running it on our local machine. We’ll need to pass in the required environment settings (note: adjust the values to your local settings, i.e. your local Postgres user etc).
Terminal
Fantastic, looks like the release is working. If we navigate to http://localhost:4000/products, everything looks good:
Onto the docker image!
Create the Docker image
The first step is to install Docker desktop if not already installed.
Once that is all good, we need a Dockerfile.
Terminal
/Dockerfile
This is almost an exact replica of the Dockerfile from https://akoutmos.com/post/multipart-docker-and-elixir-1.9-releases/.
A few differences:
- Since there is now an Elixir 1.9 docker image available, we use it instead of building Elixir from source.
- Instead of starting our application directly via
CMD ["./prod/rel/...."]
we use a startup script (start_commands.sh
) via a DockerENTRYPOINT
. This is so we can run migrations prior to application startup.
We need to create the start_commands.sh
script.
Terminal
/start_commands.sh
We are calling out to a release tasks module prior to starting the application. We don’t have access to mix
tasks in our release, so we need to create a module to run the migrations.
Let’s create the module now.
Terminal
/lib/release_tasks.ex
Pretty simple, all we are doing is running thru the migrations via Ecto.Migrator
.
We’ve added new code to our application, so we need to rebuild the release in order to include it.
Terminal
Choose to over-write the existing release:
Good to go… let’s build our Docker image!
Terminal
Sweet!
Let’s test that the Docker image works locally. In order to also test that the migration in our start up script works, let’s drop and re-create the database.
Terminal
Terminal
Now we’ll run the image.
Terminal
We see the migrations get run and then the application is started. We can view our products page via http://localhost:4000/products
We have no products, since we dropped and re-created the database, but if you create a few products, you’ll see that everything is working, our Docker image is a success!
Summary
That wraps things up for today. Thanks to the new Elixir 1.9 release task, creating a release is straight forward. Likewise creating a Docker image from a release is pretty painless.
Next time out we’ll look at how to get our image deployed to AWS.
Thanks for reading and I hope you enjoyed the post!