Deploying a Phoenix app on Railway
I built a small app using Phoenix LiveView and deployed it on Railway. I encountered some minor roadblocks. Here is what I did. Hopefully, this will be useful.
Deployment
-
Create a project from my GitHub repository. At this stage, Railway would decide it's an Elixir project and automatically configured deployment workflow.
-
Right-click on the Railway project canvas, then select “Database” and choose “Add PostgreSQL.”
-
Set up environment variables as instructed in this section of the documentation. This section lists
SECRET_KEY_BASE,LANG,LC_CTYPE,DATABASE_URL, andECTO_IPV6.
- Interestingly, I've set
LANGandLC_CTYPEtoen_US.UTF-8. But I'm still seeing this error:LC_ALL: cannot change locale (en_US.UTF-8). It seems harmless for now. - This list also seems incomplete. To make a Phoenix LiveView app work, you need to add the following variables:
PHX_SERVERandPHX_HOST. (You can also checkruntime.exsfor these settings.)- Set
PHX_SERVERtotrue. - Set
PHX_HOSTtomy-app.up.railway.app. (I'll usemy-appas a placeholder name.) - If you don't set
PHX_SERVER, you'll see this error message in the logs:Configuration :server was not enabled for HaveYourBackWeb.Endpoint, http/https services won't start. - If you don't set
PHX_HOSTcorrectly, incoming WebSocket requests will be rejected.
- Set
Migration
After completing the steps above, the Phoenix app is running and successfully connects to the hosted Postgres database. However, the database remained empty. It turned out that no part of the building and deploying process explicitly ran the migrations.
After some trial and error, I got the migrations working by doing the following:
- In your codebase, create a file at
lib/my_app/release.exwith aMyApp.Releasemodule, and define a function to run migrations:
1defmodule MyApp.Release do
2 @app :my_app
3
4 def migrate do
5 Application.load(@app)
6 for repo <- Application.fetch_env!(@app, :ecto_repos) do
7 {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
8 end
9 end
10end
- In
lib/my_app/application.ex, add a conditional to run migrations in production.
1defmodule MyApp.Application do
2 use Application
3
4 @impl true
5 def start(_type, _args) do
6
7 # BEGIN ADDED
8 if Application.get_env(:my_app, :sql_sandbox) == false do
9 MyApp.Release.migrate()
10 end
11 # END ADDED
12
13 # Existing code.
14 end
- Then, in
mix.exs, add the following configuration:
1defmodule HaveYourBack.MixProject do
2 use Mix.Project
3
4 def project do
5 [
6 app: :my_app,
7 # Existing code.
8 deps: deps(),
9
10 # BEGIN ADDED
11 releases: [
12 my_app: [
13 include_executables_for: [:unix],
14 applications: [runtime_tools: :permanent]
15 ]
16 ]
17 # END ADDED
18 ]
19 end
20
21 # Existing code.
- Finally, add this command to the
Custom Start Commandfield underSettings->Deploy.
/app/_build/prod/rel/my-app/bin/my-app eval "MyApp.Release.migrate" && \
/app/_build/prod/rel/my-app/bin/my-app start
