NetrunHome

Sites and apps

How to deploy a Phoenix app without a VPS

· 5 min read

In short

You can deploy a Phoenix app without renting a server: upload the source code together with the mix.exs file, and the platform installs the dependencies, builds the project and gives you a public HTTPS link. Three things decide whether the link answers: the app has to listen on the port from the PORT environment variable and bind to 0.0.0.0, SECRET_KEY_BASE has to be set as an environment variable, and if the app runs as a compiled release you have to switch the server on with PHX_SERVER=true, otherwise it starts with no errors and listens on nothing. Leave the _build and deps folders out of the upload: dependencies are installed during the build.

Elixir hosting usually starts with renting a server: install the right Erlang and Elixir versions, build a release, put a database next to it, add nginx in front, issue a certificate and make sure everything comes back after a reboot. Phoenix itself is often ready on the first evening, while the deploy takes a couple of weekends that have nothing to do with your code.

Netrun takes that part away: you upload the folder with your source code, the platform sees mix.exs in the root, installs the dependencies, builds the project and gives you a public HTTPS link. No server, no nginx, no manual certificate. Below are six steps between a folder of code and a working link, plus the three places where Elixir projects usually trip up.

  1. Ship the sources and mix.exs#

    The mix.exs file in the project root is what tells the platform this is Elixir and what dependencies to install; keep mix.lock next to it so the library versions match yours. Leave the _build and deps folders out of the archive: they are the result of a build on your machine, they only inflate the upload and sometimes get in the way. Compiled assets in priv/static do not need to travel either, they are built during publishing.

  2. Read the port from the environment#

    The platform picks the port and passes it in the PORT environment variable, and the app has to bind to 0.0.0.0 rather than localhost. In practice this is one edit in config/runtime.exs: read the value with System.get_env("PORT") and pass it to the endpoint together with ip: {0, 0, 0, 0}. A hard-coded 4000 is the number one reason the link does not open later. Note that the environment has to be read in config/runtime.exs: config/prod.exs runs at build time, when the secrets do not exist yet.

  3. Set SECRET_KEY_BASE as a secret#

    In production Phoenix refuses to start without SECRET_KEY_BASE, the value that signs sessions and cookies. Generate it with mix phx.gen.secret and hand it to the platform: during project setup Netrun asks for secrets and injects them as environment variables in encrypted form, so they never sit in the archive or the repository. If your app builds absolute URLs or sends email, add the project address as PHX_HOST after the first publish, otherwise those links keep pointing at localhost.

  4. Switch the server on if the app runs as a release#

    This is the classic Elixir trap. When the app starts as a compiled release instead of mix phx.server, the Phoenix generators leave a condition in config/runtime.exs: the web server only starts if the PHX_SERVER variable is set. Without it the app boots happily, the logs show no errors at all, nothing listens on the port and the link returns nothing. Add PHX_SERVER=true to the project secrets: it does no harm to mix phx.server and it makes a release answer.

  5. Decide where the database and the data live#

    There is no managed database service here, so there are two working paths: use an external database, or run PostgreSQL as a second service in your own docker-compose file, with up to five services in one project. Either way keep the connection string in secrets, in the DATABASE_URL variable, not in a config file. Ecto migrations are easiest to run at application startup: there is no SSH access into the container and no terminal in the dashboard, so there is nowhere to run a one-off command. Files the app writes itself belong in the persistent /data folder, because ordinary files next to the code are recreated on the next publish.

  6. Upload the code and grab the link#

    Bring the project as a ZIP archive or import a GitHub repository, including a private one. You do not need Erlang and Elixir installed anywhere or a release built by hand, it all happens on our side, and styles and scripts are built during publishing as long as mix.exs has the standard assets.deploy alias from the Phoenix generators. The first build takes longer than an update because dependencies compile from scratch. Logs and publishing status are visible in the dashboard in real time, and once the app is up you get a public HTTPS link.

The result: your Phoenix site or API opens at an ordinary link you can send to anyone, with no server to rent and no certificates to configure. The free plan gives you one project, and a site on it sleeps while idle and wakes up by itself when someone opens the link, so the first visit after a pause is a little slower. If the app has to answer without delay around the clock, or you want to attach your own domain, the Pro plan fits; current prices and limits are shown in your dashboard. Try Netrun.

Common questions

Do I need to run mix release before uploading?

No, upload the source code: dependencies are installed and the project is built on our side from mix.exs. You do not need Erlang or Elixir on your machine and you do not need to run build commands. If you already have your own Dockerfile that builds a release, the platform uses it instead of the automatic build, and in that case remember the PHX_SERVER variable.

Why did Phoenix start but the site does not open?

Most often the app listens in the wrong place: the port is hard-coded as 4000 instead of coming from PORT, or the endpoint binds to localhost instead of 0.0.0.0. The second common cause is a release running without PHX_SERVER, so the app works but its web server is off and the logs stay clean. The third is reading settings in config/prod.exs instead of config/runtime.exs, because that file runs at build time when the secrets do not exist yet.

Where do I get SECRET_KEY_BASE and where should it go?

Generate the value with mix phx.gen.secret on your own machine. Do not put it in the code or in a config file that travels to the repository — hand it to the platform as a secret and it is injected into the SECRET_KEY_BASE environment variable in encrypted form. The same secret signs sessions, so changing it logs everyone out.

How do I connect PostgreSQL and run Ecto migrations?

Use an external database or run one as a second service in your own docker-compose file, and pass the connection string as a secret in the DATABASE_URL variable. Migrations are easiest to run at application startup, for example through the standard Release module the Phoenix generators create, called before the app starts. There is no other place to run them: the container has no SSH access and the dashboard has no terminal.

Does LiveView work?

Yes. The persistent LiveView connection goes through the link you get, with no extra websocket setup on your side. Keep the free plan in mind: the site sleeps while idle, and an open tab reconnects by itself once the next visit wakes it up. If you cannot afford those gaps, the Pro plan runs without idle downtime.

Can I run a background worker in Elixir with no web part?

Yes, an app without a web endpoint runs too, for example an Oban queue worker or your own process with a schedule inside it. There is no separate task scheduler on the platform, so keep the schedule inside the application. On the free plan such projects do not sleep, but they do not run forever either: they get three days, after which the project is switched off while the code, settings and secrets stay in place.

Which languages and technologies are supported?

Python, Node.js, Go, Rust, Ruby, PHP, Java, .NET, Deno, Bun, Elixir, static sites and bash scripts. You can bring your own Dockerfile or docker-compose, but more often the stack is detected from your code automatically.

Do I need my own server or VPS?

No. Netrun deploys your project on its own servers. There is no VPS to rent, no systemd or nginx to configure and no certificates to renew — all of that runs for you.

Why does my website sometimes sleep?

On the free plan, websites sleep after being idle and wake up in a couple of seconds on the first request. On the Pro plan your project runs without sleeping.

How do I update the code of a project that is already published?

Open the project and upload a new version or update it from GitHub. The link stays the same, and your data is kept as long as it lives in persistent storage — the /data folder or a volume from your compose file.

Get your own project online

Upload your code, answer a couple of questions and get a working link. There is a free plan

Try Netrun
All blog articles