Is there a Phoenix VPS Deployment Guide?


I can’t find any comprehensive guide on deploying Phoenix on a VPS with PostgreSQL, Nginx, Ubuntu 18.04. Anybody knows?


Hey @Nvim! What are you looking for that isn’t in the official guide?

I’d note that you probably don’t need Nginx as the Cowboy webserver used by Phoenix is plenty fast enough for serving static assets etc. :slight_smile:


@Ipil Thanks I’ll check this out just not familiar how Cowboy performs.

1 Like

If you have any problems post here. Good luck!





Ok I’m taking this opportunity. ^^
I have an umbrella project that contains 2 web apps (frontend and admin).
In the official deployment guide there is no specific path for an umbrella project.
So in my case do I have to deploy the 2 phoenix web apps separetely or as a whole one project.
I just want to be sure if I will be doing the things the right way.
In my understanding it is better to deploy them separetely. Is it ok?

Also in my case, since the 2 web apps will be running on the same VPS, I think I will need something like HAProxy (unless I want to keep some :PORT in the admin app urls for example).
Am I right? If I am, do you think NGINX is a better choice than HAProxy for that?



I’m a big fan of keeping everything as simple as possible so instead of effectively serving two applications on two ports I would mount one application on the other, possibly by using Phoenix’s forward macro to redirect requests to the /admin path to the admin app’s router module
This way you’re only deploying one web application and the admin app gets requests under /admin for example.

1 Like

If you want to go with running your app in Docker, then a better choice is Traefik

The Cloud Native Edge Router

A reverse proxy / load balancer that’s easy, dynamic, automatic, fast, full-featured, open source, production proven, provides metrics, and integrates with every major cluster technology… No wonder it’s so popular!

The best of all in this Traefik setup is that the generation and renewal of LetsEncrypt certificates is done on the fly.

An example docker compose file:

version: "2.1"


    image: phoenix/app
    build: ./docker
    restart: always
    env_file: .env
      - MIX_ENV=prod
      - traefik
      - default
    command: "elixir -S mix phx.server"
      - "$PWD:/home/elixir/workspace"
      - "traefik.enable=true"
      - "traefik.backend=elixir-app-name"
      - ""
      - "traefik.port=4000"
      - ""

external: true

NOTE: This file is not tested and is an adaptation of a current one for a Python server

To setup Traefik on the Host just follow the docs about LetsEncrypt & Docker.

1 Like

I would not under any circumstances describe traefik as a “better” tool than nginx or haproxy - particularly for a single host. It trades away at least a decade worth of hardening and optimizing for a mildly easier configuration format, and even that is subjective. Nor should anyone advocate using docker-compose for serious deployments.

Both nginx/haproxy would serve well at splitting one port number to two applications by some combination of host name and/or path, and will otherwise stay out of your way very well. It’s still trivial to automate LetsEncrypt with either one. I would ultimately still aim for @lpil’s suggestion of keeping to a single deployment if possible, though, and you’ll find Cowboy very capable in its own right.


Any benchmark of Cowboy vs Nginx?

1 Like

I wrote a guide on how to do a CI build/deploy using ansible:

We’ve been using this setup to automate deploys to digital ocean. Here’s the sample repo:

It doesn’t cover provisioning, but we use ansible for that too (nginx, mariadb, letsencrypt, etc).


Not readily available, but I would argue that it wouldn’t be a useful comparison for your workload as you’ll always be using cowboy for the application requests so it’s only static assets that nginx will be serving by itself. The rest of the time (during the requests you care about more) it’ll be a reverse proxy adding a miniscule amount of latency to the Cowboy + Elixir response time.

Cowboy is used to proxy every request to applications deployed on Heroku, and previously (I’m unsure if it still is) was used to serve GitHub pages, so that’s some anecdotal evidence about performance. If you are concerned about performance the area to focus on will be you’re application and your database queries rather then the web server when using Phoenix+Cowboy.


Hey @Nvim - i am a bit late to the party and I didn’t cover Postgre, but a non-docker deployment howto can be found here:

At the bottom is a gist:

This will install nginx in Front of your app. The Erlang VM will be supervised as systemd service.

For your setup (two apps under one umbrella) I’d simply create two backends in nginx and route either a different (sub-)domain for admin/frontend or just rewrite paths.

I’d go for different domains like


Just create another “Server” in nginx and point it to your admin backend. Add another backend to your backend.conf as well.

Happy to help! Let me know if this is helpful.


This is helpful just need to test this on my VPS will update you when it’s successful. Thanks a lot!

1 Like

Yes it would be very simpler if I can serve the admin app through the front app, but unfortunately each web app has its own endpoint. In Phoenix.Router forward/4 docs I just read :

Note, however, that we don’t advise forwarding to another endpoint. The reason is that plugs defined by your app and the forwarded endpoint would be invoked twice, which may lead to errors.

In my former Phoenix project, I kept admin and front apps in the same web app, but I found the project folder structure a bit overloaded.
Separating them in two different apps makes me feel a lot more comfortable. I am even able to easily build completely different assets structures with webpack config.

1 Like

You can mount the router rather than the endpoint :slight_smile:

You may need to move some plugs around depending on your setup but it’s less work than deploying both, and you can write a unit test for the mounted routes to ensure its all working as intended

1 Like

I am no deployment master, by no means, but I never found necessary using any platform specific tools for deployment. For all my deployment tasks git was always good enough. Bonus is whatever language or framework you ever want to try you will never need to learn another deployment tool (which is almost never simple to use).


Hi @Nvim . I’ve recently covered this on a screencast series for a Phoenix 1.4 Chat Server. Since the deployment part is behind a paywall, I’ll share the source for my Edeliver & Nginx setup here, on the thread.

There’s another video on YT that gives an overview of how it works.

If you go the route @shanesveller suggested of just running Cowboy without Nginx, then you might want to take a look at this Stack Overflow answer, so you can run it from ports 80 and 443.

Also, Digital Ocean has a number of excellent guides that may be just what you’re looking for!

Hope this is helpful!


I think my problem is that I still do not quite understand some features of Phoenix components.
Thanks to your suggestion, I just made some search on how not to mount a Phoenix endpoint and I found this blog post that gives me a lot of details on one easy way to implement Routing in Phoenix Umbrella Apps.
it’s a bit outdated, but I should be able to adapt it to Phoenix 1.4 projects.

To quickly sum up the post, I will add a third minimal Phoenix app (proxy_app) to the umbrella project. That app will be the main point of contact to the outside world (or one of the existing app could also play this role as you suggested).

Since this app will be the actual web server, we should disable the server setting in the other two:

# apps/client/config/config.exs
config :client, Client.Web.Endpoint, server: false

# apps/admin/config/config.exs
config :admin, Admin.Web.Endpoint, server: false

# apps/proxy/config/config.exs
config :proxy, Proxy.Endpoint, server: true

The proxy app endpoint will look like following:

defmodule Proxy.Endpoint do
  use Phoenix.Endpoint, otp_app: :proxy

  @base_host_regex ~r|\.?mydomain.*$|
  @subdomains %{
    "admin" => Admin.Web.Endpoint,
    "client" => Client.Web.Endpoint
  @default_host Client.Web.Endpoint

  def init(opts), do: opts

  def call(conn, _) do
    with subdomain <- String.replace(host, @base_host_regex, ""),
         endpoint <- Map.get(@subdomains, subdomain, @default_host) do, endpoint.init())

If anyone think there is any problem with that way of handling the Routing , please let me know. ^^

1 Like

Looks good to me. :slight_smile:

1 Like