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!
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.
We must create the ./Dockerfile
on the directory root to run locally:
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.
-
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.
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 we will see this page:
Let's change it to make it unique.
lib/render_deploy_web/templates/page/index.html.heex
line 2 change
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
to
<h1><%= gettext "Welcome to %{name}!", name: "Render deploy" %></h1>
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:
# -----------------
# 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:
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 and create the project, if you don't have an account, create it then click on New, Web Service.
Select your repository:
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
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.
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 🎉
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.
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 and the application that we've just built here: 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.
Top comments (0)