Elixir/Phoenix Project: Dockerizing
Tags: elixir, tutorial, sideprojects
Published: 2020-10-14 15:23:09

#### Goals:
  - Dockerize project
  - Update README with new instructions on launching app with Docker

#### Dockerize project
Although this is implied with all of my writing, it's especially true with Docker, so it bears to be said explicitly: I am by no means a Docker expert. What I will be talking about in this post are the steps I took to get my project running in Docker from online resources and my past experience. If there is any aspect of what I'm about to talk about that could be improved, I would greatly appreciate any feedback. With that caveat out of the way, here is what I did to build my app in Docker images. 

Actually, before I dive into the files, here's a quick refresher on whats what in the world of Docker:
- A `Dockerfile` builds an _image_
- A _container_ is started from that image
- `docker-compose.yml` defines which containers (aka services) make up a complete app

The first step was to create a `Dockerfile`, which is a file of specifications that Docker uses to build an image. For my `Dockerfile` I used an image from [Docker Hub](https://hub.docker.com/) that I have used in the past, [alpine-elixir-phoenix](https://hub.docker.com/r/bitwalker/alpine-elixir-phoenix/). I have been happy with it, and I'm also familiar with how to setup a `Dockerfile` with it. I used the image [docs](https://hub.docker.com/r/bitwalker/alpine-elixir-phoenix/) as a starting point for my `Dockerfile` but ended up simplifying it a bit for my needs:

{% gist https://gist.github.com/noelworden/364fa34c45d54050d91f963db10559fa %}

Heres the breakdown of the file:

{% gist https://gist.github.com/noelworden/78662a7f0dbc8b869029df6a5fcae3f2 %}

This is declaring which version of elixir will be used.

{% gist https://gist.github.com/noelworden/96403d8cc26f103a701ff1302c15749f %}

This is executing the `RUN` command to create a new directory ,`/app`. Then `WORKDIR` is setting that newly created folder as the working directory.

{% gist https://gist.github.com/noelworden/da891ff61f264aed49f8f1dce22ca7cb %}

This is executing the `COPY` command to move the `mix.exs` and `mix.lock` files from the local directory into the image directory, then using `RUN` to execute a `mix` command in the image directory.

{% gist https://gist.github.com/noelworden/7f539b45f4739c3d62d38260b97e3818 %}

Similar to the previous command, this is executing `COPY` to move the necessary `assets` from the local directory to the image, then `RUN` to execute an `npm install --prefix` into the folder on the image.

{% gist https://gist.github.com/noelworden/5dc8db200387cac494bbf1cce192c172 %}

This copies the rest of the files and folders from the root of the local directory to the root of the image.

{% gist https://gist.github.com/noelworden/36456b7e81cfc10522c36edf0e158725 %}

Lastly, an execution of `CMD` to start the server

With the `Dockerfile` complete, the next step was to create a `docker-compose.yml` file. Like I said earlier, this file defines the containers that will make up the complete app. There's a good amount of content, so I'll utilize comments in the example code to explain what is what, instead of listing everything out individually:

{% gist https://gist.github.com/noelworden/e1dd6ddd5d3850dbb06b49f4502bbea8 %}

One last thing that I needed to do before taking this for a test run was to change to the `dev.exs` file. This change was not on _any_ of the tutorials I used, and it caused me enough trouble that when I found the solution I actually wrote up a standalone [post](https://dev.to/noelworden/docker-connecting-an-elixir-phoenix-app-container-and-database-container-2n0a) about it. The change is pretty painless, the `hostname` field of the `Repo` config needs to be updated from `localhost` to `db` to match the name of the container running the database:

{% gist https://gist.github.com/noelworden/9fcd95b204b342cbcef83b91fcdc09b4 %}

With the two docker files created, the update to `dev.exs`, and [Docker for Mac](https://docs.docker.com/docker-for-mac/install/) installed an running on my machine, the app is up and running with three commands (I recommend executing in two tabs of the terminal):

- Terminal Tab 1
  - `docker-compose build`
  - `docker-compose up`

Once its spun up, the `db` container shows the following error:

{% gist https://gist.github.com/noelworden/fb9866e633ed469a3c17ebe255bfde45 %}

Although the `db` container is up and running, the app is looking for the database that is declared in `dev.exs`, in this case `atlas_dev`. The third command will take care of this:

- Terminal Tab 2
  - `docker-compose exec web mix ecto.setup`

That command uses `exec web` to execute the `mix` command in the currently running `web` container. The `ecto.setup` command is from the `mix.exs` file, and runs a list of commands:

{% gist https://gist.github.com/noelworden/3f1652f05991616dda9d60401d85be4b %}

Although I don't have any migrations or seeds at the moment, I still use that command, it's mostly a muscle memory thing at this point.

With that command executed in a second tab, the logs in the first tab no longer display an error, and everything is ready to go. The app can now be viewed at [localhost:4000](http:localhost:4000). Of course, since I have yet to actually code anything in this new app, it's still the generic Phoenix startup page, but that will change soon.

#### Update README with new instructions on launching app with Docker
With the project Dockerized, I also updated the README with new instructions on how to spin up the app:

---

## Spinning up project with Docker
  - Install [Docker for Mac](https://docs.docker.com/docker-for-mac/install/)
  - In one terminal tab:
    - `docker-compose build`
    - `docker-compose up`
  - In a second tab, once the two commands in the first tab are completed:  
    - `docker-compose exec web mix ecto.setup`
  - If everything spins up with no errors, site will be live at [localhost:4000](http://localhost:4000)

---

It's essentially the steps from above, but I believe that keeping docs updated is important, so it's worth noting as part of my process

#### Bonus - Ignoring files for a faster Docker build
A file can be created that is similar to what `.gitignore` does for git, but for Docker. This file is `.dockerignore` and Docker looks for this file before each build to see if there are files that can be skipped in the execution of the build. Skipping files creates a smaller image and therefore a faster build. I pulled the contents of my `.dockerignore` from the Distillery [hexdocs](https://hexdocs.pm/distillery/guides/working_with_docker.html):

{% gist https://gist.github.com/noelworden/9f2b35c6399a1a1fe0c18f2ad07e9b00 %}

Adding this file was one of my last steps in the process, and I kind of regret that, as the build is _noticeably_ faster with Docker ignoring these files. Adding this earlier in the process would have saved me a lot of time while experimenting with the proper `Dockerfile` and `docker-compose.yml` configurations.

#### Closing thoughts

Docker will be the last devops aspect I am going to setup. I am ready to do some feature coding and get that default Phoenix splash page out of there. But, I am very happy that I have taken the time to set my project up with these tools and make the process as streamlined as possible right out of the gate. From here on out I can basically just concern myself with features and whatever random thing I want to try, and all the checks I have in place will keep my code tidy and easy to deploy. 

[This](https://github.com/noelworden/atlas/tree/782b262ceef922082c0b66fb324262fecf41b7e3) is my repo at the end of this block of work.

#### References
- https://docs.docker.com/engine/reference/builder/
- https://hub.docker.com/r/bitwalker/alpine-elixir-phoenix/
- https://medium.com/swlh/use-docker-to-create-an-elixir-phoenix-development-environment-e1a81def1d2e
- https://codereviewvideos.com/course/elixir-phoenix-and-docker-tutorial/video/docker-compose-tutorial-for-elixir-and-phoenix-framework
- https://codefresh.io/docker-tutorial/not-ignore-dockerignore-2/


------

This post is part of an ongoing [series](https://dev.to/noelworden/new-side-project-new-blog-series-135h). I encourage any critique, feedback, or suggestions in the comments.