How to Serve Multiple Domains in a Single Phoenix App

Multiple buds in a line coming off a single tree branch

Elixir makes managing your digital product easier, especially when it’s time to scale. Book a free consult today to learn how it can put you ahead of the competition.

Some applications require the ability to serve multiple domains, which is typically accomplished through the use of subdomains or by hosting multiple distinct sites.

Take YouTube, for instance. They have many subdomains such as music.youtube.com and studio.youtube.com, and also different domains like youtube.com and youtubekids.com. While we can’t say for sure how this is implemented by them, in this blog, we will demonstrate how to configure a single Phoenix application to handle multiple domains and subdomains. Fortunately, Phoenix makes the process simple and straightforward.

First, let’s take a look at this example inside the router.ex file:

scope "/", MyAppWeb do
  live "/music", MusicLive
  live "/video", VideoLive
end

These are routes that the user could access through the following URLs: myapp.com/music and myapp.com/video. But imagine we want to point the user to the MusicLive page if they access it through the subdomain music.myapp.com and to the VideoLive when video.myapp.com. We could also have a subdomain for all admin content.

Here’s an example of how to define routes grouped together by a single subdomain.

# partial host match - match subdomain `music.`, i.e., matches `music.myapp.com`
scope "/", MyAppWeb, host: "music." do
  live "/", MusicLive
end

# partial host match - match subdomain `video.`, i.e., matches `video.myapp.com`
scope "/", MyAppWeb, host: "video." do
  live "/", VideoLive
end

# partial host match - match subdomain `admin.`, i.e., matches `admin.myapp.com`
scope "/", MyAppWeb, host: "admin." do
  live "/", AdminLive, :home
  live "/settings", AdminSettingsLive # admin.myapp.com/settings
end

This uses the scope/4 macro with the host option to group routes under the music. and video. subdomains. The live/4 macro then defines a path and the name of the LiveView to render the page.

We can also define routes grouped under one or more completely different hosts if we include the top-level domain:

# full host match
scope "/", MyAppWeb, host: "my-example.org" do
  live "/", HomeLive, :new
end

The host option also accepts a list instead of a string to match multiple different hosts or subdomains.

scope "/", MyAppWeb, host: ["guest.", "example.com", "example2.com"] do
  live "/", AnotherHomeLive, :new
end

You also need to pass the :check_origin option when configuring your endpoint explicitly outlining which origins are allowed, otherwise you will end up getting a Phoenix.Socket transport error.

Edit config/runtime.exs and edit the following config inside config :my_app, MyAppWeb.Endpoint, ...:

config :my_app, MyAppWeb.Endpoint, 
 check_origin: [
    "https://myapp.com/",
    "https://example.com",
    "https://example2.com",
    "https://my-example.org"
  ],
# ... Truncated for demonstration purposes

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box