Vela supports Phoenix apps out of the box via BEAM releases.
In your Phoenix project:
# Install dependencies and compile
MIX_ENV=prod mix deps.get
MIX_ENV=prod mix compile
# Build assets (if using esbuild/tailwind)
MIX_ENV=prod mix assets.deploy
# Build the release
MIX_ENV=prod mix releaseThis creates a self-contained release in _build/prod/rel/my_app/ that includes the BEAM runtime. No Erlang or Elixir installation needed on the server.
[app]
name = "my-app"
domain = "my-app.example.com"
[deploy]
server = "root@your-server.example.com"
type = "beam"
binary = "bin/server"
health = "/health"
strategy = "sequential" # Recommended for SQLite apps
drain = 10
[env]
DATABASE_PATH = "${data_dir}/my-app.db"
SECRET_KEY_BASE = "${secret:SECRET_KEY_BASE}"
PHX_HOST = "my-app.example.com"
PHX_SERVER = "true"Key points:
type = "beam"tells Vela this is a BEAM release, started withbin/server startstrategy = "sequential"avoids two instances fighting over the SQLite databasePHX_SERVER = "true"ensures the Phoenix endpoint starts (not just the app)
vela secret set my-app SECRET_KEY_BASE=$(mix phx.gen.secret)MIX_ENV=prod mix release
vela deploy ./_build/prod/rel/my_appAdd a health check route to your router:
# lib/my_app_web/router.ex
scope "/health", MyAppWeb do
get "/", HealthController, :index
end# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
use MyAppWeb, :controller
def index(conn, _params) do
send_resp(conn, 200, "ok")
end
endOr as a simple plug:
# lib/my_app_web/endpoint.ex
plug :health_check
defp health_check(%{request_path: "/health"} = conn, _opts) do
conn |> send_resp(200, "ok") |> halt()
end
defp health_check(conn, _opts), do: connVela sets the PORT environment variable. Configure your endpoint to use it:
# config/runtime.exs
config :my_app, MyAppWeb.Endpoint,
http: [port: System.get_env("PORT") |> String.to_integer()],
server: truePoint your repo at the persistent data directory:
# config/runtime.exs
config :my_app, MyApp.Repo,
database: System.get_env("DATABASE_PATH", "my_app.db")Set DATABASE_PATH in your Vela.toml:
[env]
DATABASE_PATH = "${data_dir}/my-app.db"The data/ directory persists across deploys. Your database is safe.
Use Vela's pre_start deploy hook to run migrations before the new instance starts. If the migration fails, the deploy aborts and the old instance stays running.
[deploy]
pre_start = "bin/my_app eval 'MyApp.Release.migrate()'"Define the release module:
defmodule MyApp.Release do
def migrate do
for repo <- Application.fetch_env!(:my_app, :ecto_repos) do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
endThe hook runs with the same environment as your app (DATABASE_PATH, secrets, etc.), so Ecto connects to the right database.
Instead of managing Postgres yourself, declare it in your Vela.toml:
[services.postgres]
version = "17"
databases = ["my_app_prod"]Vela installs PostgreSQL on the server, creates the database and user, and injects DATABASE_URL into your app automatically. No manual setup needed.
Configure your repo to use it:
# config/runtime.exs
config :my_app, MyApp.Repo,
url: System.get_env("DATABASE_URL")For apps that use NATS messaging:
[services.nats]
version = "2.10"
jetstream = trueVela downloads, configures, and supervises a NATS server. NATS_URL is injected into your app.
Build your Elixir release on the server instead of locally:
[build]
remote = true
command = """
mix deps.get --only prod
MIX_ENV=prod mix compile
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix release
"""
[build.env]
MIX_ENV = "prod"With remote builds, you don't pass an artifact — just run vela deploy. Source is uploaded via git archive and built on the server.
This is useful when you don't have the BEAM installed locally or when the server architecture differs from your laptop.
- WAL mode: Enable SQLite WAL mode for better concurrent read performance. Set
journal_mode: :walin your repo config. - Graceful shutdown: Phoenix handles
SIGTERMby default. Vela sendsSIGTERMduring drain. - Logs:
vela logs my-app -ftails the app's stdout/stderr via journald.