Deploying Elixir LiveView for free on Render using Docker
Tags: elixir, tutorial, devops, webdev
Published: 2022-05-09 10:54:20


Deploying an Elixir application has been complicated some time ago, but now we have so many tools that facilitate our job that is very simple to set up a production environment for free.

Render is a platform as a service that offers a host for our server, DDoS protection, auto-deploy on git, and many more features that we can use without putting a credit card! They support Docker as well, so we are going to use it. 

The objective here is to create a simple LiveView application, change it and deploy it on Render using a Docker image.

So let's start!

![road](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/517q6sue5u3y7d83m8t9.jpg)

## Creating the app and running on docker locally

Our application will be called Render Deploy, it has no functionality, it is just to demonstrate the deployment.

To create a LiveView app, run the command below:

`mix phx.new render_deploy --live --no-ecto`

- - - live: This is the indication that the new project is a LiveView app.
- - - no-ecto: Indicates that we don't want ecto, so it will not have a Repo.

Now, on the config/dev.exs, line 12 change `127, 0, 0, 1` to `0, 0, 0, 0` to allow access from docker.

![dev.exs config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1aa6bch1ykwkn1rvhh5.png)

We must create the `./Dockerfile` on the directory root to run locally:

```docker
FROM hexpm/elixir:1.13.0-erlang-23.3.4.10-alpine-3.14.3 AS base

WORKDIR /render_deploy

RUN mix do local.hex --force, local.rebar --force

RUN apk add npm inotify-tools
```

- This is a docker multistage build, it keeps the file more readable and creates a smaller image, you can read more [here](https://docs.docker.com/develop/develop-images/multistage-build/).
- `FROM hexpm/elixir:1.13.0-erlang-23.3.4.10-alpine-3.14.3 AS base`: Here we are pulling the elixir image based on alpine, which is a smaller image.
- `WORKDIR /render_deploy`: This will be our work directory, inside the container.
- `RUN mix do local.hex --force, local.rebar --force`: Installs precompiled hex and rebar.
- `RUN apk add npm inotify-tools`: Live reloading tool

We could run docker build and docker run, but I prefer using docker-compose to manage the containers, so let's create a new `./docker-compose.yml` on the directory root.

```yaml
version: "3.8"

services:
  app:
    build:
      context: .
      target: base
    container_name: render_deploy_web
    command: mix phx.server
    restart: unless-stopped
    ports:
      - 4000:4000
    volumes:
      - .:/render_deploy
```

- With multistage build we can set a specific target when creating the container, in this case, is `target: base`.

After creating the files we can run these commands to install dependencies, create the container and run the server locally.

`docker-compose run --rm app mix deps.get`

`docker-compose up`

If we can access [localhost:4000](http://localhost:4000) we will see this page:

![phoenix home screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/swmgk3mt5hombmpx8qqn.png)

Let's change it to make it unique.

`lib/render_deploy_web/templates/page/index.html.heex` line 2 change 

```bash
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
to
<h1><%= gettext "Welcome to %{name}!", name: "Render deploy" %></h1>
```

![changed home screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vw1qv56m9ibq5zvuzb16.png)

## Adjusting the code before deploy

We are going to use mix releases, which is a way to package our application into an executable binary and use it to deploy the application.

For this, we must add the following code to the end of `./Dockerfile` file:

```bash
# -----------------
# BUILD
# -----------------
FROM base AS build

RUN apk add curl bash git

ARG MIX_ENV=prod
ENV MIX_ENV=$MIX_ENV
COPY . ./

# install application
RUN mix do deps.get, compile

# -----------------
# RELEASE
# -----------------
FROM build AS release

# digests and compresses static files
RUN mix assets.deploy

# generate release executable
RUN mix release

# -----------------
# PRODUCTION
# -----------------
FROM alpine:3.14.3

WORKDIR /render_deploy

ARG MIX_ENV=prod

# install dependencies
RUN apk add ncurses-libs curl

COPY --from=release /render_deploy/_build/$MIX_ENV/rel/render_deploy ./

# start application
CMD ["bin/render_deploy", "start"]
```

- Important things about the code:
- `ARG MIX_ENV=prod`: We set our mix env to prod.
- `RUN mix assets.deploy`: minify esbuild and compresses static files.
- `COPY --from=release /render_deploy/_build/$MIX_ENV/rel/render_deploy ./`: Copy the previous build stage to the current stage.
- `CMD ["bin/render_deploy", "start"]`: It will start the application.

Now, before deploying we must adjust our `config/runtime.exs` so Render will put the production URL: 

```elixir
import Config

if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do
  config :render_deploy, RenderDeployWeb.Endpoint, server: true
end

if config_env() == :prod do
  secret_key_base =
    System.get_env("SECRET_KEY_BASE") ||
      raise """
      environment variable SECRET_KEY_BASE is missing.
      You can generate one by calling: mix phx.gen.secret
      """

  port = String.to_integer(System.get_env("PORT") || "4000")

  config :render_deploy, RenderDeployWeb.Endpoint,
    url: [host: System.get_env("RENDER_EXTERNAL_HOSTNAME") || "localhost", port: 80],
    http: [
      ip: {0, 0, 0, 0, 0, 0, 0, 0},
      port: port
    ],
    secret_key_base: secret_key_base

  config :render_deploy, RenderDeployWeb.Endpoint, server: true
end
```

## Preparing the deploy

Ok, we have our development setup done with docker and we are ready to deploy in production with render.com

First of all, we need to create a repository with the code on Github.

Now let's access [render.com](http://render.com) and create the project, if you don't have an account, create it then click on New, Web Service.

![render create new account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egm3ziilo3ebph1d4xwi.png)

Select your repository:

![Selecting repository](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0irhs08dpm397nnlxkgf.png)

Choose a name, select Docker as the environment runtime and the free plan.

Now we must set the secret key, to do this, run the following command to generate a secret:

`docker-compose run --rm app mix phx.gen.secret`

![choosing name and env](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/75zf8lsdhikdafpy96nn.png)

After this, scroll down the page and click on "Advanced", then put the SECRET_KEY_BASE with the secret generated before.

Now we just click on "Create Web Service" and wait for Render to deploy our application to production.

![putting secret key base env](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct4kpb4sobv6s9hd88eg.png)

![clicking on create new service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0j4i48ybnh2tvkmt2e0.png)

When the deployment is completed you will see the "Live" label, now just click on the URL and check our new LiveView application running on the cloud 🎉

![deployment completed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3kpb9mwp19airi1bom64.png)

![application on cloud url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sl91m4uuh2d4jchuazrb.png)

Render has some limitations on the free plan, the apps are automatically spun down after 15 minutes of inactivity and a new request after this time can take 30 seconds to be up again, also the free plan allows for 750 hours of running time per month across all free web services in your account, you can see all limitations [here](https://render.com/docs/free).

Even with these constraints, it is a nice platform to have our services and to practice the production deployment.

You can check the source code here: [https://github.com/Lgdev07/render_deploy](https://github.com/Lgdev07/render_deploy) and the application that we've just built here: [https://render-example-71rp.onrender.com/](https://render-example-71rp.onrender.com/)

I appreciate everyone who has read through here, if you guys have anything to add, please leave a comment.