Phoenix is an Elixir web framework built on the battle-tested BEAM virtual machine, offering real-time capabilities through LiveView and unmatched fault tolerance via OTP supervision trees. This guide covers deploying a Phoenix application with releases for production use on a VPS.
Installing Elixir and Phoenix
# Install Erlang and Elixir
sudo apt install erlang elixir
# Or use asdf for version management
asdf plugin add erlang
asdf plugin add elixir
asdf install erlang 27.0
asdf install elixir 1.17.0-otp-27
asdf global erlang 27.0
asdf global elixir 1.17.0-otp-27
# Install Phoenix
mix local.hex --force
mix archive.install hex phx_new --force
Creating a Phoenix Application
# Create new project
mix phx.new myapp --database postgres
cd myapp
# Setup database
mix ecto.create
mix ecto.migrate
# Start development server
mix phx.server
Project Structure
myapp/
├── lib/
│ ├── myapp/ # Business logic (contexts)
│ │ ├── accounts.ex # Accounts context
│ │ └── accounts/
│ │ └── user.ex # User schema
│ ├── myapp_web/ # Web layer
│ │ ├── controllers/
│ │ ├── live/ # LiveView modules
│ │ ├── components/
│ │ ├── router.ex
│ │ └── endpoint.ex
│ ├── myapp.ex
│ └── myapp_web.ex
├── config/
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ └── runtime.exs # Runtime configuration
├── priv/
│ ├── repo/migrations/
│ └── static/
└── mix.exs
Building a Release
# Configure runtime.exs for production
# config/runtime.exs
import Config
if config_env() == :prod do
config :myapp, MyApp.Repo,
url: System.get_env("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
config :myapp, MyAppWeb.Endpoint,
url: [host: System.get_env("PHX_HOST"), port: 443, scheme: "https"],
http: [
ip: {0, 0, 0, 0},
port: String.to_integer(System.get_env("PORT") || "4000")
],
secret_key_base: System.get_env("SECRET_KEY_BASE")
end
# Build the release
MIX_ENV=prod mix deps.get --only prod
MIX_ENV=prod mix compile
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix release
# The release is at _build/prod/rel/myapp/
# It includes the BEAM VM — no Erlang/Elixir needed on the server
Deploying the Release
# Copy release to VPS
tar czf myapp.tar.gz -C _build/prod/rel/myapp .
scp myapp.tar.gz user@vps:/opt/myapp/
# On VPS
cd /opt/myapp && tar xzf myapp.tar.gz
# Set environment variables
export DATABASE_URL="ecto://user:pass@localhost/myapp_prod"
export SECRET_KEY_BASE=$(mix phx.gen.secret)
export PHX_HOST="example.com"
export PORT=4000
# Run migrations
./bin/myapp eval "MyApp.Release.migrate"
# Start the server
./bin/myapp start
Systemd Service
[Unit]
Description=Phoenix Application
After=network.target postgresql.service
[Service]
Type=exec
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp start
ExecStop=/opt/myapp/bin/myapp stop
EnvironmentFile=/opt/myapp/.env
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
LiveView for Real-Time UIs
# Phoenix LiveView provides real-time UI without JavaScript
# lib/myapp_web/live/dashboard_live.ex
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
if connected?(socket), do: :timer.send_interval(5000, self(), :tick)
{:ok, assign(socket, users_count: Accounts.count_users())}
end
@impl true
def handle_info(:tick, socket) do
{:noreply, assign(socket, users_count: Accounts.count_users())}
end
@impl true
def render(assigns) do
~H"""
<div class="dashboard">
<h1>Dashboard</h1>
<p>Active users: <%= @users_count %></p>
</div>
"""
end
end
Docker Deployment
FROM elixir:1.17-alpine AS builder
RUN apk add --no-cache build-base git
WORKDIR /app
ENV MIX_ENV=prod
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod && mix deps.compile
COPY config config
COPY lib lib
COPY priv priv
COPY assets assets
RUN mix assets.deploy && mix release
FROM alpine:3.20
RUN apk add --no-cache libstdc++ openssl ncurses-libs
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/myapp ./
USER nobody
CMD ["bin/myapp", "start"]
Summary
Phoenix on Elixir provides fault-tolerant, real-time web applications backed by the BEAM VM. OTP releases create self-contained deployable packages that include the runtime. LiveView eliminates the need for complex JavaScript frameworks for real-time features. The BEAM VM excels at handling thousands of concurrent connections with minimal memory, making it ideal for real-time applications, chat systems, and IoT platforms on modest VPS hardware.