Deploying an Elixir LiveView application to DigitalOcean's new App Platform

Justin Tormey / 2021-03-31
new
liveview
deployment

Introduction

DigitalOcean has long been a favorite tool for developers looking to quickly get an application up and running in the cloud. Compared to larger cloud providers like GCP and AWS, DigitalOcean offers simple UX and low costs for servers.

However this simplicity comes at a cost. Once you (the developer) have a server of your very own up and running, it’s up to you to figure out how to deploy and run your software on it.

Thankfully, DigitalOcean has offered a solution with their recently released App Platform. Through App Platform, any containerized application can be deployed to DigitalOcean in minutes, complete with a managed database if needed. In this article, you’ll learn how to quickly and easily deploy a brand new Elixir LiveView app, with an attached database, to App Platform.

Prerequisites

Before beginning, make sure you have Elixir installed, and have created a DigitalOcean account.

Initialization

First, create a new Phoenix project, with LiveView enabled:

mix phx.new --app my_app --live my-digitalocean-app

From here, you can cd into your project and verify that everything is working by starting the server.

I’ve added links as checkpoints so that you can verify what you’re doing matches the example project I created for this article. See commit.

It’s worth removing some of the Phoenix boilerplate as well (and maybe add some functionality of your own), since the search feature included by default will only work when the project is compiled for :dev. See commit.

Configuration

Next we’ll want to configure our new application so that it can run in the cloud, talk to a database, and so on. Many of these steps are covered in the Phoenix releases guide.

First, create a config/releases.exs file, and delete config/prod.secret.exs as well as where it’s mentioned in config/prod.exs. We won’t be needing a secrets file for what we’re doing.

In config/prod.exs, configure your Endpoint so that the server starts automatically in production:

config :my_app, MyAppWeb.Endpoint,
  server: true,
  # other config...

In config/releases.exs, configure your Endpoint and Repo as follows:

import Config

config :my_app, MyAppWeb.Endpoint,
  http: [:inet6, port: System.fetch_env!("PORT") |> String.to_integer()],
  url: [scheme: "https", host: System.fetch_env!("HOST"), port: 443],
  secret_key_base: System.fetch_env!("SECRET_KEY_BASE"),
  live_view: [
    signing_salt: System.fetch_env!("LIVE_VIEW_SIGNING_SALT")
  ]

config :my_app, MyApp.Repo,
  url: System.fetch_env!("DB_DATABASE_URL"),
  maintenance_database: System.fetch_env!("DB_DATABASE"),
  ssl: true,
  pool_size: 10

A quick summary of the env vars we’re using here:

  • PORT - The port our application should start on, will be configured on DO
  • HOST - The host our application will run on, provided by DO
  • SECRET_KEY_BASE - A secret value used for securing requests
  • LIVE_VIEW_SIGNING_SALT - A secret value used for securing LiveView connections
  • DB_DATABASE_URL - Connection string for connecting to our database, provided by DO
  • DB_DATABASE - The name of our database, provided by DO

When configuring your Repo, don’t forget to add ssl: true, as DigitalOcean requires a secure database connection to be used. On top of that, we need to specify maintenance_database, which is used by our application to perform operations such as migrations. By default, it will use postgres, however DigitalOcean does not provision databases with a postgres database, causing our Repo to fail when normally it should not.

To generate secure secrets for SECRET_KEY_BASE and LIVE_VIEW_SIGNING_SALT, use the command provided by Phoenix: mix phx.gen.secret.

Lastly, copy this Dockerfile to your project and push your changes to a new GitHub repository. The Dockerfile is identical to the one provided in the Phoenix releases documentation.

DigitalOcean

Ok, with our project set up and on GitHub, we’re ready for the fun part. Head over to DigitalOcean to create a new app.

Select GitHub from the options (authenticating if you haven’t already) and choose the repo that you pushed the project to:

On the next step, we can leave all the defaults as-is, but add the following environment variables:

HOST = ${APP_DOMAIN}
SECRET_KEY_BASE = # value that you generated
LIVE_VIEW_SIGNING_SALT = # other value that you generated
DB_DATABASE_URL = ${do-app-platform-example-postgres.DATABASE_URL}
DB_DATABASE = ${do-app-platform-example-postgres.DATABASE}

Besides the values that you generated, these env vars are provided by DigitalOcean. See the guide on using environment variables in App Platform here for more information. Note that we do not have to specify PORT, as this is automatically available in all web apps.

It’s also during this step that you may choose to add a database to your app. The name that you pick here for your database is what’s used to select the right values for our database env vars above.

Once this is done, the remaining steps are straightforward, simply name your service and select what plan you want (the lowest cost one is fine). After you’ve chosen your plan, press “Launch” and watch your app build for the first time!

(This article didn’t cover running automatic database migrations, however this is described in detail in the Phoenix releases guide).

Bonus: DNS Setup

At this point, your app should be running on a domain generated by DigitalOcean. But, what if we want to run our app on a custom domain? Thankfully DigitalOcean makes this easy for us. Navigate to your app settings (the last tab on the right), click on “Edit” next to the “Domains” setting, and press “+ Add Domain”.

DigitalOcean gives you the option to manage your own domain, or to let them manage it for you. I opt to manage my own domain, as I find it to be easier. Once this panel is selected, copy the CNAME Alias they give you and configure it in your domain registrar per DigitalOcean’s instructions. The change will take a few minutes to through, but your app will be hosted on your custom domain shortly after!

Note: It’s possible to host an app on DigitalOcean App Platform on multiple domains, however this won’t work right away with LiveView. This is because the app HOST is configured for only one of these domains, and LiveView has a “check-origin” feature, which requires the provided host to match the host that the user is on while interacting with LiveView. To fix this, you may add an explicit :check_origin option to your LiveView config. See this commit for how that might look.