Skip to main content

Deploy Phoenix

This guide will help you deploy a Phoenix (Elixir) application to Deployxa.

Overview

Phoenix is a productive web framework for Elixir that makes developers happy. Deployxa provides optimized deployments for Phoenix with automatic configuration.

Features:

  • Automatic framework detection
  • Elixir 1.15+ support
  • PostgreSQL support
  • Phoenix Channels (WebSocket)
  • LiveView support
  • Automatic HTTPS
  • Global CDN

Prerequisites

  • A Phoenix application (version 1.7 recommended)
  • A GitHub repository with your code
  • A Deployxa account
  • PostgreSQL database

Quick Start

1. Create Your Phoenix App

If you don't have a Phoenix app yet:

mix phx.new my_phoenix_app
cd my_phoenix_app

2. Configure for Production

Update config/runtime.exs:

import Config

if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
"""

config :my_phoenix_app, MyPhoenixApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise """
environment variable SECRET_KEY_BASE is missing.
"""

host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "4000")

config :my_phoenix_app, MyPhoenixAppWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [
ip: {0, 0, 0, 0, 0, 0, 0, 0},
port: port
],
secret_key_base: secret_key_base
end

3. Push to GitHub

git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/yourusername/my_phoenix_app.git
git push -u origin main

4. Deploy to Deployxa

  1. Go to Deployxa Dashboard
  2. Click "New Project"
  3. Select your GitHub repository
  4. Deployxa will automatically detect Phoenix
  5. Click "Deploy"

Your app will be live in under 60 seconds!

Configuration

Detected Settings

Deployxa automatically detects these settings:

SettingValue
FrameworkPhoenix
Build Commandmix deps.get && mix compile && mix assets.deploy
Output Directory_build
Start Commandmix phx.server
Port4000

Custom Configuration

If you need to customize settings:

  1. Go to ProjectSettingsBuild
  2. Modify settings as needed
  3. Save changes

Example Custom Settings:

{
"buildCommand": "mix deps.get && mix compile && mix assets.deploy",
"startCommand": "mix phx.server"
}

Environment Variables

Adding Environment Variables

  1. Go to ProjectEnvironment
  2. Click "Add Variable"
  3. Enter key and value
  4. Click "Save"

Required Phoenix Environment Variables:

SECRET_KEY_BASE=your-secret-key-base-here
PHX_HOST=your-app.deployxa.app
PORT=4000

# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname
POOL_SIZE=10

Generating SECRET_KEY_BASE

Generate a new secret key base:

mix phx.gen.secret

Or use Elixir:

elixir -e 'IO.puts(:crypto.strong_rand_bytes(64) |> Base.encode64)'

Database Configuration

Using PostgreSQL

Phoenix uses PostgreSQL by default. Configure in config/runtime.exs:

database_url = System.get_env("DATABASE_URL")

config :my_phoenix_app, MyPhoenixApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

Database Migrations

Run migrations automatically on deployment:

Option 1: Release Script

Create rel/overlays/bin/migrate:

#!/bin/sh
cd -P -- "$(dirname -- "$0")"
exec ./my_phoenix_app eval "MyPhoenixApp.Release.migrate"

Option 2: Manual Migration

Run migrations via SSH or console:

mix ecto.migrate

Database Seeding

Seed your database after migration:

mix run priv/repo/seeds.exs

Asset Management

Deploying Assets

Compile and deploy assets:

mix assets.deploy

This command is automatically run by Deployxa during deployment.

Using esbuild

Phoenix uses esbuild by default for asset compilation:

mix assets.build

Using Tailwind CSS

Phoenix includes Tailwind CSS support:

mix tailwind.install
mix tailwind default

Custom Domains

Adding a Custom Domain

  1. Go to ProjectDomains
  2. Click "Add Domain"
  3. Enter your domain (e.g., example.com)
  4. Configure DNS records
  5. Wait for verification

DNS Configuration

For Apex Domain (example.com):

Type: A
Name: @
Value: 76.76.21.21

For Subdomain (www.example.com):

Type: CNAME
Name: www
Value: cname.deployxa.com

Performance Optimization

Enable Connection Pooling

Configure pool size in config/runtime.exs:

config :my_phoenix_app, MyPhoenixApp.Repo,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

Enable Caching

Use ETS for caching:

defmodule MyPhoenixApp.Cache do
use GenServer

def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end

def get(key) do
GenServer.call(__MODULE__, {:get, key})
end

def put(key, value) do
GenServer.cast(__MODULE__, {:put, key, value})
end

def init(state) do
{:ok, state}
end

def handle_call({:get, key}, _from, state) do
{:reply, Map.get(state, key), state}
end

def handle_cast({:put, key, value}, state) do
{:noreply, Map.put(state, key, value)}
end
end

Database Indexing

Add indexes to frequently queried columns:

# priv/repo/migrations/xxxx_add_index_to_users.exs
defmodule MyPhoenixApp.Repo.Migrations.AddIndexToUsers do
use Ecto.Migration

def change do
create index(:users, [:email])
end
end

Monitoring

Viewing Logs

  1. Go to ProjectLogs
  2. Select log type:
    • Build Logs: Deployment process
    • Runtime Logs: Application output
    • Error Logs: Application errors

Phoenix Logging

Configure logging in config/config.exs:

config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

Use logging in code:

require Logger

def index(conn, _params) do
Logger.info("Home page accessed")
render(conn, "index.html")
end

Monitoring Metrics

  1. Go to ProjectAnalytics
  2. View metrics:
    • Request count
    • Response times
    • Error rates
    • Bandwidth usage
    • CPU/RAM usage

Health Checks

Deployxa automatically monitors your application:

  • HTTP health checks every 30 seconds
  • Automatic restart on failure
  • Alerts on consecutive failures

Add a health check endpoint:

# lib/my_phoenix_app_web/controllers/health_controller.ex
defmodule MyPhoenixAppWeb.HealthController do
use MyPhoenixAppWeb, :controller

def index(conn, _params) do
json(conn, %{status: "healthy"})
end
end
# lib/my_phoenix_app_web/router.ex
scope "/api", MyPhoenixAppWeb do
pipe_through :api
get "/health", HealthController, :index
end

Troubleshooting

500 Internal Server Error

Problem: Application returns 500 error

Solution:

  1. Check runtime logs for errors
  2. Verify environment variables
  3. Check database connection
  4. Ensure SECRET_KEY_BASE is set
  5. Check for missing dependencies

Database Connection Failed

Problem: DBConnection.ConnectionError

Solution:

  1. Verify DATABASE_URL is correct
  2. Check database host and port
  3. Ensure database exists
  4. Check firewall rules
  5. Verify database user permissions

Compilation Failed

Problem: mix compile fails

Solution:

  1. Check build logs for specific errors
  2. Verify dependencies are correct
  3. Check for syntax errors
  4. Test build locally
  5. Ensure Elixir version is compatible

Assets Not Loading

Problem: CSS/JS files return 404

Solution:

  1. Run mix assets.deploy
  2. Check priv/static directory exists
  3. Verify asset paths are correct
  4. Check file permissions

Connection Timeout

Problem: Requests timeout

Solution:

  1. Increase pool size
  2. Optimize database queries
  3. Enable connection pooling
  4. Upgrade plan for more resources
  5. Check for long-running queries

Best Practices

Project Structure

my_phoenix_app/
├── config/
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ └── runtime.exs
├── lib/
│ ├── my_phoenix_app/
│ │ ├── application.ex
│ │ └── repo.ex
│ └── my_phoenix_app_web/
│ ├── controllers/
│ ├── views/
│ └── router.ex
├── priv/
│ ├── repo/
│ │ └── migrations/
│ └── static/
├── test/
├── mix.exs
├── .env.example
└── README.md

Environment Variables

  • Never commit .env file
  • Use .env.example as template
  • Rotate secrets regularly
  • Use different values per environment

Security

  • Keep Phoenix updated
  • Use HTTPS only
  • Validate all inputs
  • Use parameterized queries
  • Enable CSRF protection
  • Use Comeonin for password hashing
  • Keep dependencies updated

Performance

  • Use connection pooling
  • Enable caching
  • Optimize database queries
  • Use ETS for in-memory caching
  • Use CDN for static assets
  • Enable compression

Database

  • Use migrations for schema changes
  • Index frequently queried columns
  • Use transactions for complex operations
  • Backup database regularly
  • Use connection pooling

Advanced Configuration

Custom Runtime Configuration

# config/runtime.exs
import Config

if config_env() == :prod do
# Database
database_url =
System.get_env("DATABASE_URL") ||
raise "DATABASE_URL not set"

config :my_phoenix_app, MyPhoenixApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
ssl: true,
ssl_opts: [
verify: :verify_peer,
cacertfile: "/etc/ssl/certs/ca-certificates.crt"
]

# Secret key base
secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise "SECRET_KEY_BASE not set"

# Host and port
host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "4000")

config :my_phoenix_app, MyPhoenixAppWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [
ip: {0, 0, 0, 0, 0, 0, 0, 0},
port: port
],
secret_key_base: secret_key_base,
server: true
end

LiveView

For real-time interfaces:

# lib/my_phoenix_app_web/live/page_live.ex
defmodule MyPhoenixAppWeb.PageLive do
use MyPhoenixAppWeb, :live_view

@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end

@impl true
def handle_event("increment", _, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end

@impl true
def render(assigns) do
~H"""
<div>
<h1>Count: <%= @count %></h1>
<button phx-click="increment">Increment</button>
</div>
"""
end
end

Phoenix Channels

For WebSocket communication:

# lib/my_phoenix_app_web/channels/user_socket.ex
defmodule MyPhoenixAppWeb.UserSocket do
use Phoenix.Socket

channel "room:*", MyPhoenixAppWeb.RoomChannel

@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end

@impl true
def id(_socket), do: nil
end
# lib/my_phoenix_app_web/channels/room_channel.ex
defmodule MyPhoenixAppWeb.RoomChannel do
use MyPhoenixAppWeb, :channel

@impl true
def join("room:lobby", _payload, socket) do
{:ok, socket}
end

@impl true
def handle_in("new_msg", %{"body" => body}, socket) do
broadcast!(socket, "new_msg", %{body: body})
{:noreply, socket}
end
end

Examples

Basic Phoenix App

# lib/my_phoenix_app_web/controllers/page_controller.ex
defmodule MyPhoenixAppWeb.PageController do
use MyPhoenixAppWeb, :controller

def index(conn, _params) do
render(conn, "index.html")
end
end

REST API

# lib/my_phoenix_app_web/controllers/user_controller.ex
defmodule MyPhoenixAppWeb.UserController do
use MyPhoenixAppWeb, :controller

alias MyPhoenixApp.Accounts

def index(conn, _params) do
users = Accounts.list_users()
json(conn, users)
end

def create(conn, %{"user" => user_params}) do
with {:ok, user} <- Accounts.create_user(user_params) do
conn
|> put_status(:created)
|> json(user)
end
end
end

With Database

# lib/my_phoenix_app/accounts/user.ex
defmodule MyPhoenixApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset

schema "users" do
field :name, :string
field :email, :string

timestamps()
end

def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email])
|> validate_required([:name, :email])
|> unique_constraint(:email)
end
end
# priv/repo/migrations/xxxx_create_users.exs
defmodule MyPhoenixApp.Repo.Migrations.CreateUsers do
use Ecto.Migration

def change do
create table(:users) do
add :name, :string
add :email, :string

timestamps()
end

create unique_index(:users, [:email])
end
end

Resources


Need Help? Contact support@deployxa.com or join our community Discord.