← Blog

Deploying Phoenix when your job queue is already inside the release

Railyard

October 14, 2026

Most frameworks need a second deployable process just to run background jobs — a worker dyno, a separate container, something to keep in sync with the web app’s build. Phoenix usually doesn’t, because Oban runs as part of the same OTP supervision tree as the web endpoint itself: the job runner isn’t a second app, it’s already alive the moment web boots.

What’s read from mix.exs

  • Datastores are detected from dependencies, not declared twice: ecto_sql/postgrex provisions Postgres, ecto_ch/ch provisions ClickHouse, redix provisions Redis.
  • SECRET_KEY_BASE is generated once, not left for you to produce and store yourself.
  • MIX_ENV=prod and PHX_SERVER=true are set automatically — the two environment flags a compiled Phoenix release actually needs to serve traffic instead of sitting idle.
railyard create my-app https://github.com/me/my-app --addons postgres

or the same Git URL from the dashboard once GitHub’s connected.

The part that’s genuinely different about compiled releases

A compiled Elixir release has no mix at runtime — the migration command that works in development (mix ecto.migrate) doesn’t exist in the artifact that actually ships. Railyard finds wherever the release puts its own migrator and runs that instead (bin/migrate if the release defines one, otherwise bin/<app> eval), and mix phx.server becomes bin/<app> start the same way. That distinction — dev command versus what the compiled release actually exposes — is the single most common thing that trips up a first Elixir deploy on any platform, and it’s handled before the first migration ever runs.

If a dedicated worker node is actually worth it

Oban needs no second process for most apps — but if job volume is high enough that a slow queue could start starving web requests, splitting it onto its own node is still a one-line addition, not an architecture change:

web:    bin/my_app start
worker: bin/my_app eval "MyApp.Release.worker()"
railyard scale my-app worker=2

Same release, same build, started differently — add or edit it from the Workers tab with no repo change required.

Config and secrets

Everything config/runtime.exs reads with System.get_env/2 — BASE_URL, HTTP_PORT, LISTEN_IP, DATABASE_URL, CLICKHOUSE_DATABASE_URL and anything else — is a plain var under Variables, or set via railyard env:set my-app KEY=VALUE, never baked into the compiled release. The same release runs unchanged from staging through production.

Full reference: Phoenix and Elixir docs, Workers and scheduled jobs.

Push a Phoenix app right now.

mix.exs is read automatically — ecto.migrate, SECRET_KEY_BASE and the database all wired up.

Free plan, no card, and 14 days of Pro for every new team. One flat price per team.