Deploying an Elixir LiveView application to DigitalOcean's new App Platform
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.