Skip to content

Latest commit

 

History

History
208 lines (151 loc) · 4.64 KB

File metadata and controls

208 lines (151 loc) · 4.64 KB

Deploying Elixir/Phoenix Apps

Vela supports Phoenix apps out of the box via BEAM releases.

Build a Release

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 release

This 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.

Vela.toml

[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 with bin/server start
  • strategy = "sequential" avoids two instances fighting over the SQLite database
  • PHX_SERVER = "true" ensures the Phoenix endpoint starts (not just the app)

Set Secrets

vela secret set my-app SECRET_KEY_BASE=$(mix phx.gen.secret)

Deploy

MIX_ENV=prod mix release
vela deploy ./_build/prod/rel/my_app

Health Check

Add 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
end

Or 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: conn

Listening on the Right Port

Vela 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: true

SQLite with Ecto

Point 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.

Migrations

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
end

The hook runs with the same environment as your app (DATABASE_PATH, secrets, etc.), so Ecto connects to the right database.

Postgres with Services

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")

NATS with Services

For apps that use NATS messaging:

[services.nats]
version = "2.10"
jetstream = true

Vela downloads, configures, and supervises a NATS server. NATS_URL is injected into your app.

Remote Builds

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.

Tips

  • WAL mode: Enable SQLite WAL mode for better concurrent read performance. Set journal_mode: :wal in your repo config.
  • Graceful shutdown: Phoenix handles SIGTERM by default. Vela sends SIGTERM during drain.
  • Logs: vela logs my-app -f tails the app's stdout/stderr via journald.