# Deploy Phoenix to a VPS: mix release and Postgres Deploy an Elixir Phoenix app to your own VPS: ox builds a mix release with its assets, runs Release.migrate, starts it under systemd, and adds PostgreSQL. To deploy Phoenix to a VPS with ox, add the repo as a project and deploy it, with no Dockerfile and no ox.toml. ox reads `mix.exs` and `lib/hello/release.ex`, runs `mix local.hex --force && mix local.rebar --force && MIX_ENV=prod mix deps.get --only prod`, `mix compile`, `mix assets.deploy`, `mix release` and `_build/prod/rel/hello/bin/hello eval 'Hello.Release.migrate()'` for the migrations, then starts `_build/prod/rel/hello/bin/hello start` under systemd behind Caddy with HTTPS, and sets up postgres 18 for it. Run `ox check` in your repo to see the same plan before the first deploy. **Plan verified, real-server test pending.** The plan below is what `ox check` prints for a minimal Phoenix repo in ox's tests. A deploy on a real server is not recorded yet. ox reads `mix.exs` and `lib/hello/release.ex`, builds the plan on the card, and starts `_build/prod/rel/hello/bin/hello start` on your server. The cylinders are the services the repo asked for. ## What ox detects in a Phoenix repo This is the plan `ox check` prints for a minimal an Elixir Phoenix app, with no ox.toml. Each row says where its value came from: a file in the repo, or ox's default. | Field | Value | Source | | --- | --- | --- | | `app.start` | `PHX_SERVER=true RELEASE_TMP=/tmp RELEASE_DISTRIBUTION=none PHX_HOST=$PUBLIC_HOST _build/prod/rel/hello/bin/hello start` | `detected:mix.exs` | | `build.install` | `mix local.hex --force && mix local.rebar --force && MIX_ENV=prod mix deps.get --only prod` | `detected:mix.exs` | | `build.commands[0]` | `MIX_ENV=prod mix compile` | `detected:mix.exs` | | `build.commands[1]` | `MIX_ENV=prod mix assets.deploy` | `detected:mix.exs` | | `build.commands[2]` | `MIX_ENV=prod mix release` | `detected:mix.exs` | | `build.migrate` | `RELEASE_TMP=/tmp _build/prod/rel/hello/bin/hello eval 'Hello.Release.migrate()'` | `detected:lib/hello/release.ex` | | `packages` | `elixir` | `detected:mix.exs` | | `packages` | `erlang-dev` | `detected:mix.exs` | | `packages` | `erlang-nox` | `detected:mix.exs` | | `services.postgres` | `postgres 18 (shared)` | `detected:mix.exs` | `ox check` also prints these hints for it: - `not installed: erlang 27.3.4 (from .tool-versions): ox installs Erlang/OTP 27 and Elixir 1.18.3 from Ubuntu's archive` - `not installed: elixir 1.18.4-otp-27 (from .tool-versions): ox installs Erlang/OTP 27 and Elixir 1.18.3 from Ubuntu's archive` ## The ox.toml for Phoenix None is needed: the plan above comes from the repo alone. Write one to serve your own domain or to change what ox detected. This one passes `ox check` against the same repo: `[app]` keeps the detected start command and build. Once a repo has an ox.toml, ox adds no service itself, so list each one under `[services]`. ox.toml, optional: ```toml domains = ["www.example.com"] [app] [services] postgres = {} ``` ## Check and deploy Phoenix Add the repo as a project, then check and ship it from a terminal: Check, deploy and read the logs: ```sh ox check # in the repo: the plan above, offline ox new --server # add the repo as a project ox deploy --wait # stream the deploy, exit with its result ox logs --follow # the app's own logs ``` ## Variables to set for Phoenix Set `SECRET_KEY_BASE` before the first deploy: `ox check` asks for it, and the deploy stops until it has a value. Use the dashboard's Variables tab or `ox vars set KEY`, which asks for the value. ox provides these to the app itself: `PORT`, `HOST`, `OX_ENV`, `OX_PROJECT`, `OX_RELEASE`, `OX_DATA_DIR`, `PUBLIC_URL`, `PUBLIC_HOST` and `DATABASE_URL`. ## If the Phoenix deploy fails - `set these before deploying: SECRET_KEY_BASE`: A variable the app needs has no value. Set it on the dashboard's Variables tab, or with `ox vars set KEY`. [More](https://deploywithox.com/docs/operate/variables#set). - `config/runtime.exs does not read PORT: set the endpoint's http port to String.to_integer(System.get_env("PORT")), as mix phx.new does`: Read the port from `PORT` in `config/runtime.exs`, as a new Phoenix app does. [More](https://deploywithox.com/docs/troubleshooting#health-check-failed). - `no Release.migrate in lib/, so ox runs no migrations: run mix phx.gen.release to add one`: Run `mix phx.gen.release` once and commit the module it writes. [More](https://deploywithox.com/docs/config#build). Your visitors never see a failed deploy: the previous release keeps serving until the new one passes its health check. ## Next steps - [Set variables and secrets](https://deploywithox.com/docs/operate/variables#set) on the dashboard or with `ox vars`. - [The `[app]` keys](https://deploywithox.com/docs/config#app): start, health check, memory and the rest. - [Service dashboards](https://deploywithox.com/docs/operate/services): your database's status, size, backups and data. - [Add a custom domain](https://deploywithox.com/docs/operate/domains) with one A record; Caddy gets the HTTPS certificate. - [Read the logs](https://deploywithox.com/docs/operate/logs) live, search them, or download them. - [Set variables and secrets](https://deploywithox.com/docs/operate/variables) on the dashboard or with `ox vars`. ## Phoenix FAQ ### Do I need a Dockerfile or an ox.toml to deploy Phoenix to a VPS? No. ox does not use Docker, and `ox check` on a Phoenix repo with no ox.toml prints `Ready to deploy.` Write an ox.toml only to change what ox detected. ### What does ox install on the server for Phoenix? Only what the plan names: the apt packages `elixir`, `erlang-dev` and `erlang-nox`. Your app's own dependencies are installed by the build into the release folder, not system-wide. ### Which Erlang and Elixir does ox install? The ones in Ubuntu's archive, Erlang/OTP 27 and Elixir 1.18.3, as the hints above say. A different version in `.tool-versions` is reported, not installed. ## Related guides - [**Spring Boot (Maven)**Deploy a Spring Boot app built with Maven to your own VPS: ox builds the jar with mvnw, installs Java 21, runs it under systemd and adds PostgreSQL for it.](https://deploywithox.com/docs/guides/spring-boot) - [**Spring Boot (Gradle)**Deploy a Spring Boot app built with Gradle to your own VPS: ox runs gradlew bootJar, reads the Java version from build.gradle.kts, and runs the jar.](https://deploywithox.com/docs/guides/spring-boot-gradle) Every stack ox deploys, and the ones it does not yet, is on the [Stacks page](https://deploywithox.com/docs/guides). Last updated 2026-10-09. The page as HTML: https://deploywithox.com/docs/guides/phoenix