# Dockerize Phoenix + Tailwind application

## Preamble

I'm hoping you already have a Phoenix application running If you happen to hop here directly.

If not, you may visit the previous two parts where we went through Creating a [simple phoenix application](https://manju.dev/creating-phoenix-application), and [how to set up Tailwind with phoenix](https://manju.dev/setting-up-tailwind-css-in-a-phoenix-application).

## Build a production-ready app and run it locally

In this article, I'm directly going to jump into setting up the application to make it production ready by

* Testing it locally in production mode (`env=prod`)
    
* Run the app in production mode to ensure our build process is correct,
    
* And then use the same instructions to dockerize the application
    

There is an excellent series written by @[Miguel Cobá](@miguelcoba) on how to prepare [Phoenix application deployment with Elixir Releases](https://blog.miguelcoba.com/preparing-a-phoenix-16-app-for-deployment-with-elixir-releases) and also, official [elixir release](https://hexdocs.pm/phoenix/releases.html) documentation.

However, the main reason I'm writing this article is that I bump into some obstacles to configure a Dockerfile to build the production-ready application. Here, I will go through the issues I have had in detail.

### Setup, Compile In prod mode

```plaintext
// Initial setup
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
```

With this, you may notice that there is a new folder created under `_build/` called `prod`

```plaintext
// Compile assets
$ MIX_ENV=prod mix assets.deploy

// lets test the build with production mode
$ PORT=4000 MIX_ENV=prod SECRET_KEY_BASE=$(mix phx.gen.secret)  mix phx.server

// You will see the following instructions
╰─$ PORT=4000 MIX_ENV=prod SECRET_KEY_BASE=$(mix phx.gen.secret)  mix phx.server
00:21:54.461 [info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
00:21:54.468 [info] Access DockerPhoenixTailwindWeb.Endpoint at http://example.com
```

If you are wondering about the env variable `SECRET_KEY_BASE` which is a secret [Phoenix uses to sign and encrypt important information](https://hexdocs.pm/phoenix/deployment.html#handling-of-your-application-secrets).

Head over to http://localhost:4000 and ensure that the application is running.

### Generate a release for a production-ready build

<mark>This is the step which </mark> **<mark>I missed during my entire process of preparing a release </mark>** and almost spent a day and a half to figure it out. Before generating a release, we need to uncomment the following in `/config/runtime.exs`

```plaintext
config :docker_phoenix_tailwind, DockerPhoenixTailwindWeb.Endpoint, server: true
```

Essentially, it instructs Phoenix to start the webserver from the release.

Once the above line is uncommented, run the following command to generate the actual release

```plaintext
╰─$ MIX_ENV=prod mix release                                                                        
* assembling docker_phoenix_tailwind-0.1.0 on MIX_ENV=prod
* using config/runtime.exs to configure the release at runtime
* skipping elixir.bat for windows (bin/elixir.bat not found in the Elixir installation)
* skipping iex.bat for windows (bin/iex.bat not found in the Elixir installation)

Release created at _build/prod/rel/docker_phoenix_tailwind

    # To start your system
    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start

Once the release is running:

    # To connect to it remotely
    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind remote

    # To stop it gracefully (you may also send SIGINT/SIGTERM)
    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind stop

To list all commands:

    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind
```

As shown above, the release is created at `_build/prod/rel/docker_phoenix_tailwind/bin/` folder,

Let's run it and see if the release works

```plaintext
╰─$ _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start
ERROR! Config provider Config.Reader failed with:
** (RuntimeError) environment variable SECRET_KEY_BASE is missing.
You can generate one by calling: mix phx.gen.secret

    /Users/manju/Codes/github/docker_phoenix_tailwind/_build/prod/rel/docker_phoenix_tailwind/releases/0.1.0/runtime.exs:17: (file)
    (elixir 1.14.2) src/elixir.erl:309: anonymous fn/4 in :elixir.eval_external_handler/1
    (stdlib 4.2) erl_eval.erl:748: :erl_eval.do_apply/7
    (stdlib 4.2) erl_eval.erl:492: :erl_eval.expr/6
    (stdlib 4.2) erl_eval.erl:136: :erl_eval.exprs/6
    (elixir 1.14.2) src/elixir.erl:294: :elixir.eval_forms/4
    (elixir 1.14.2) lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1
    (elixir 1.14.2) lib/code.ex:425: Code.validated_eval_string/3
```

That did not work?

It's because we need to make sure to run the above command with a `SECRET_KEY_BASE` a secret which is required in production mode to encrypt important information. Let's try it again

```plaintext
╰─$ SECRET_KEY_BASE=$(mix phx.gen.secret) _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start
00:41:29.927 [info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
00:41:29.928 [info] Access DockerPhoenixTailwindWeb.Endpoint at http://example.com
```

Head over to the browser and test [http://localhost:4000/](http://localhost:4000/). The application now is running through a release executable (As we will do the same in production)

Let's sum up all the commands

```plaintext
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
$ MIX_ENV=prod mix assets.deploy
$ MIX_ENV=prod mix release
$ SECRET_KEY_BASE=$(mix phx.gen.secret) _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start
```

It's been quite a journey to get the phoenix app release to make it work locally. In the following section, let's prepare a Dockerfile with all the instructions we went through so far.

## Let's Dockerize

Initially, I followed the Phoenix release documentation to generate the [Dockerfile](https://hexdocs.pm/phoenix/releases.html#containers) automatically and try it build the image, and run the container, however, the container did not run due to incorrect instructions to build the assets and also missing `SECRET_KEY_BASE` and it took me a couple of hours to debug the Dockerfile.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672444731354/59e5fe91-97d8-4f14-a4f2-88daa08651d0.jpeg align="center")

Then I stumbled upon two issues

* Assets weren't compiling (Tailwindcss) and therefore website was broken
    
* Was running docker container without `SECRET_KEY_BASE`
    

To address the above issues, I slightly modified the **Dockerfile** instructions to build the assets correctly. This is instructed quite well in the Dockerfile comments, which I did not give much attention to.

We are not going to explore the entire Dockerfile instruction, I hope the comments are self-explanatory.

%[https://github.com/manjufy/docker_phoenix_tailwind/blob/main/Dockerfile] 

An important change to notice from the auto-generated Dockerfile is to ensure asset compilation **is after copying lib**

### Setup Asset compilation correctly

In the Dockerfile, we need to make sure to setup asset compilation correctly so that our tailwind is working correctly.

**Before.**

<mark>Notice that, in the following Docker instructions, we COPY the assets and then we immediately compile the assets, Which was causing assets not getting minified correctly.</mark>

```plaintext
# Copy assets
# note: if your project uses a tool like https://purgecss.com/,
# which customizes asset compilation based on what it finds in
# your Elixir templates, you will need to move the asset compilation
# step down so that `lib` is available.
COPY assets assets
# Compile assets
RUN mix assets.deploy

# Compile project
COPY lib lib

RUN mix compile

# Copy runtime configuration file
COPY config/runtime.exs config/

# Assemble release
COPY rel rel
RUN mix release
```

**After**

Moved instruction `RUN mix assets.deploy` after, `COPY lib lib` instruction. Otherwise, assets (JS and CSS) won't be properly minified.

```plaintext
# Copy assets
# note: if your project uses a tool like https://purgecss.com/,
# which customizes asset compilation based on what it finds in
# your Elixir templates, you will need to move the asset compilation
# step down so that `lib` is available.
COPY assets assets

# Compile project
COPY lib lib

# IMPORTANT: Make sure asset compilation is after copying lib
# Compile assets
RUN mix assets.deploy

RUN mix compile

# Copy runtime configuration file
COPY config/runtime.exs config/

# Assemble release
COPY rel rel
RUN mix release
```

### Build the image and run it from a container

Copy and create the [Dockerfle](https://github.com/manjufy/docker_phoenix_tailwind/blob/main/Dockerfile) in the project root directory.

Build the image

```plaintext
╰─$ docker image build -t elixir/docker_phoenix_tailwind .
```

Run the container

```plaintext
╰─$ docker run -e SECRET_KEY_BASE="$(mix phx.gen.secret)" -p  4000:4000 elixir/docker_phoenix_tailwind
00:17:50.396 [info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
00:17:50.396 [info] Access DockerPhoenixTailwindWeb.Endpoint at http://example.com
```

Now head over to http://localhost:4000. We now have an application running from the container which you could use to deploy in production.

The main Dockerfile is based on **Debian bullseye**, however, I have also experimented with an **alpine image** which I have explained in the [README](https://github.com/manjufy/docker_phoenix_tailwind/blob/main/README.md#dockerize-with-alpine-dockerfilealpine).

The entire application source is here

%[https://github.com/manjufy/docker_phoenix_tailwind] 

For now, that completes the series. However, in the future, I would like to explore deploying by containerising the application on different platforms such as Digitalocean, and Fly.io.
