How to Deploy Phoenix Applications to Vultr Using Nanobox

Phoenix is an incredibly powerful, fast, and flexible web framework built in Elixir. It benefits from Elixir's use of the Erlang VM, known for running low-latency, distributed and fault-tolerant systems. Vultr is relatively new cloud provider (with tons of experience) that offers high-performance SSD servers in datacenters around the world. Its simple UI makes it easy to use and love.

In this article, I'm going to walk through deploying a Phoenix application to Vultr using Nanobox. Nanobox uses Docker to build local development and staging environments, as well as scalable, highly-available production environments on Vultr.

Download Nanobox

Go ahead and create a Nanobox account and download Nanobox Desktop, the Nanobox CLI.

Setup Your Phoenix Project

Whether you have an existing Phoenix project or are starting the scratch, the process of configuring it for Nanobox is the same.

Note: In this tutorial, I'll be using Phoenix 1.3. If using 1.2, just change all the phx references to phoenix.

Add a boxfile.yml

Nanobox uses the boxfile.yml to build and configure your app's environment both locally and in production. Create a boxfile.yml in the root of your project with the following:

run.config:
  engine: elixir
  dev_packages:
    - nodejs
    - inotify-tools
  cache_dirs:
    - node_modules
  extra_path_dirs:
    - node_modules/.bin
  fs_watch: true

deploy.config:
  extra_steps:
    - mix phoenix.digest
  before_live:
    web.main:
      - mix ecto.create --quiet
      - mix ecto.migrate

data.db:
  image: nanobox/postgresql:9.5

web.main:
  start: node-start mix phx.server

This includes everything Phoenix needs to run. You may need to update a few items specific to your project, but in this walk-through, I'm going to use:

  • An Elixir runtime
  • Node.js packages and settings for Brunch asset compilation
  • Filesystem watching for local code reloading
  • Inotify for local filesystem notifications and code reloading
  • Static asset digest generation on deploy
  • Database creation/migration on deploy
  • A Postgres database
  • A web component that acts as a public webserver when deployed

Start the Local Dev Environment

With the boxfile.yml in place, you can fire up a virtualized local development environment. I recommend adding a DNS alias just so the app will be easier to access from a browser.

# Add a convenient way to access the app from a browser
nanobox dns add local phoenix.dev

# Start the dev environment
nanobox run

Nanobox will provision a local development environment, spin up a containerized Postgres database, mount your local codebase into the VM, load your app's dependencies, then drop you into a console inside the VM.

Generate a New Phoenix Project

If you have an existing Phoenix project, you can skip this section. To generate a new Phoenix project from scratch, run the following from inside the Nanobox console:

# cd into the /tmp dir to create an app
cd /tmp

# Install Phoenix
mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez

# Generate a new Phoenix application
mix phx.new app #(Do not fetch dependencies at this moment)

# cd back into the /app dir
# Enable the hidden files shell option
# Copy the generated app into the project dir
cd -
shopt -s dotglob
cp -a /tmp/app/* .

Your project's current working directory is mounted into the /app directory in the VM, so all the Phoenix files written there will propagate back down to your machine's filesystem and vice versa.

Update the Database Connection

When Nanobox spins up a Postgres database, it generates environment variables for the necessary connection credentials. Update the database connection in your config/dev.exs, config/test.exs, and config/prod.secret.exs.

# Configure your database
config :app, App.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: System.get_env("DATA_DB_USER"),
  password: System.get_env("DATA_DB_PASS"),
  hostname: System.get_env("DATA_DB_HOST"),
  database: "gonano",
  pool_size: 10

Note: The gonano database is created as a default database when Postgres is provisioned, but you're welcome to create your own and use that.

Fetch Application Dependencies

To load your app's dependencies, run the following from the root of your project:

mix deps.get

Fetch Asset Dependencies

Phoenix uses Brunch for asset compilation, which is installed as a Node.js package. Both npm and yarn are included in the nodejs extra_package. I recommend using yarn, since it's a lot faster than npm.

If you're working with an existing Phoenix project, Brunch will have been installed as your app was built and installed in the dev environment. If you started from scratch, run:

cd assets && yarn install
cd ../

Run Your Database Migration

With a fresh Postgres install, you'll need to create your database structure.

mix ecto.create

Run Phoenix Locally

With your database connection updated and dependencies loaded, you're ready to start Phoenix in your local dev environment. From the /app directory in your Nanobox console:

mix phx.server

You'll then be able to access your running Phoenix app at phoenix.dev:4000.

Whenever you exit out of the Nanobox console, it'll shut your VM down and drop you back into your host OS.

Prepare Phoenix for Deploy

Before you deploy the project, make sure the app is listening on port 8080. This is configured in your config/prod.exs:

config :app, AppWeb.Endpoint,
  http: [port: 8080],
  load_from_system_env: true,
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/manifest.json"

Starting Phoenix in Production

You should already have a web component included in the boxfile.yml above. This tells Nanobox to create a publicly accessible webserver when the app is deployed. Notice that the web component's start command is prepended with node-start.

web.main:
  start: node-start mix phx.server

node-start is a helper included in Nanobox Elixir engine that ensures nodes are started with credentials sufficient to cluster and attach to after they are running. Using this, you'll be able to scale your web component into a horizontal cluster without any extra configuration.

Alright! Now to the fun part!

Setup Your Vultr Account

If you haven't already, create a Vultr account. In your Vultr admin, select "Account" in the left-nav and open the "API" section. By default, Vultr API access is disabled. When you enable it, they will provide you with an API key.

Vultr Access Token

Vultr lets you whitelist subnets that can access the Vultr API using the generated key. To use the Nanobox Vultr integration, go ahead and click "Allow All IPv4".

Vultr IP Whitelist

Note: If you're uncomfortable whitelisting all IPv4 addresses, you can whitelist 138.197.215.155/32. This is the subnet on which the Nanobox Vultr adapter is hosted, but this subnet is subject to change. If/when it changes, you will need to manually update your Vultr account with the new subnet.

Add a New Provider to Your Nanobox Account

Add New Provider Account

Select Vultr and click "Proceed."

Select Vultr

Nanobox needs your Vultr access token to authenticate with your Vultr account. Paste in your token and click "Verify & Proceed."

Enter your Vultr access token

Name your provider and choose a default region. The name is arbitrary and only meant to help you identify it in your list of provider accounts.

Name your provider and select a default region

Launch a New App

Go to the home page of your Nanobox dashboard and click the "Launch New App" button. Select your Vultr provider from the dropdown and choose the region in which you'd like to deploy your app.

Select your Vultr provider

Confirm and click "Let's Go!" Nanobox will order a VC2 instance under your Vultr account. When the instance is up, Nanobox will provision platform components necessary for your app to run:

  • Load-Balancer: The public endpoint for your application. Routes and load-balances requests to web nodes.
  • Monitor: Monitors the health of your server(s) and application components.
  • Logger: Streams and stores your app's aggregated log stream.
  • Message Bus: Sends app information to the Nanobox dashboard.
  • Warehouse: Storage used for deploy packages, backups, etc.

Once all the platform components are provisioned and running, you're ready to deploy your app.

Stage Your App Locally

Nanobox provides "dry-run" functionality that simulates a full production deploy on your local machine. This step is optional, but recommended. If the app deploys successfully in a dry-run environment, it will work when deployed to your live environment.

nanobox deploy dry-run

More information about dry-run environments is available in the Dry-Run documentation.

Deploy

Add Your New App as a Remote

From the root of your project directory, add your newly created app as a remote.

nanobox remote add app-name

This connects your local codebase to your live app. More information about the remote command is available in the Nanobox Documentation.

Deploy to Your Live App

With your app added as a remote, you're ready to deploy.

nanobox deploy

Nanobox will compile and package your application code, send it up to your live app, provision all your app's components inside your live VC2 instance, network everything together, and BOOM! Your app will be live on Vultr.

Manage & Scale

Once your app is deployed, Nanobox makes it easy to manage and scale your production infrastructure. In your Nanobox dashboard you'll find health metrics for all your app's instances/containers. Your application logs are streamed in your dashboard and can be streamed using the Nanobox CLI.

Although every app starts out on a single VC2 instance with containerized components, you can break components out into individual servers and/or scalable clusters through the Nanobox dashboard. Nanobox handles the deep DevOps stuff so you don't have to. Enjoy!

Posted in Phoenix, Elixir, Vultr