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
- Go to Deployxa Dashboard
- Click "New Project"
- Select your GitHub repository
- Deployxa will automatically detect Phoenix
- Click "Deploy"
Your app will be live in under 60 seconds!
Configuration
Detected Settings
Deployxa automatically detects these settings:
| Setting | Value |
|---|---|
| Framework | Phoenix |
| Build Command | mix deps.get && mix compile && mix assets.deploy |
| Output Directory | _build |
| Start Command | mix phx.server |
| Port | 4000 |
Custom Configuration
If you need to customize settings:
- Go to Project → Settings → Build
- Modify settings as needed
- Save changes
Example Custom Settings:
{
"buildCommand": "mix deps.get && mix compile && mix assets.deploy",
"startCommand": "mix phx.server"
}
Environment Variables
Adding Environment Variables
- Go to Project → Environment
- Click "Add Variable"
- Enter key and value
- 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
- Go to Project → Domains
- Click "Add Domain"
- Enter your domain (e.g.,
example.com) - Configure DNS records
- 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
- Go to Project → Logs
- 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
- Go to Project → Analytics
- 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:
- Check runtime logs for errors
- Verify environment variables
- Check database connection
- Ensure SECRET_KEY_BASE is set
- Check for missing dependencies
Database Connection Failed
Problem: DBConnection.ConnectionError
Solution:
- Verify DATABASE_URL is correct
- Check database host and port
- Ensure database exists
- Check firewall rules
- Verify database user permissions
Compilation Failed
Problem: mix compile fails
Solution:
- Check build logs for specific errors
- Verify dependencies are correct
- Check for syntax errors
- Test build locally
- Ensure Elixir version is compatible
Assets Not Loading
Problem: CSS/JS files return 404
Solution:
- Run
mix assets.deploy - Check priv/static directory exists
- Verify asset paths are correct
- Check file permissions
Connection Timeout
Problem: Requests timeout
Solution:
- Increase pool size
- Optimize database queries
- Enable connection pooling
- Upgrade plan for more resources
- 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
.envfile - Use
.env.exampleas 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
Related Topics
Resources
Need Help? Contact support@deployxa.com or join our community Discord.