Let’s deploy an small HTTP service written in Elixir on Runway!
Create a new project for your Elixir app. In this example, the module is called Example
, which you have to take into account with the code presented further down:
$ mix new --module Example my_app
Then go into the my_app
directory, initialize a git repository (git init
).
Elixir uses mix
to maintain dependencies, so let’s add what we need — most notably Cowboy (an HTTP-framework for Erlang/Elixir) into the mix.exs
file:
defmodule Example.MixProject do
use Mix.Project
def project do
[
app: :example,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger],
mod: {Example.Application, []}
]
end
defp deps do
[
{:plug_cowboy, "~> 2.6"} # Plug with Cowboy server
]
end
end
Then run mix deps.get
to download all dependencies.
Define the overall application in lib/example/
directory. example
is the module name we selected earlier when we created the project.
The application.ex
is the overall entry point for our app and configures the webserver through a PORT
environment variable (with a default of 3000
):
defmodule Example.Application do
use Application
def start(_type, _args) do
children = [
# Start the Plug Cowboy HTTP server
{Plug.Cowboy, scheme: :http, plug: Example.Router, options: [port: port()]}
]
opts = [strategy: :one_for_one, name: Example.Supervisor]
Supervisor.start_link(children, opts)
end
# Fetch the port from the environment or default to 3000
defp port do
try do
System.get_env("PORT") |> String.to_integer()
rescue
_ -> 3000
end
end
end
… followed by the route definitions and the handlers to send a response to a client. Create/open (lib/example/
) router.ex
and insert the following:
defmodule Example.Router do
use Plug.Router
use Plug.Logger
plug :match
plug :dispatch
get "/" do
send_resp(conn, 200, "Hello")
end
get "/hello-world" do
send_resp(conn, 200, "Hello World")
end
match _ do
send_resp(conn, 404, "Oops, not found!")
end
end
Finally, let’s configure the logger outside the lib/example
directory and create config/config.exs
with the following contents:
use Mix.Config
config :logger, :console,
format: "[$level] $message\n",
metadata: [:request_id],
level: :info
You created/edited the following files:
lib/example/application.ex
lib/example/router.ex
config/config.exs
mix
as well: mix run --no-halt
will start the application on http://localhost:3000
.Now that all is configured and setup, let’s continue with the build.
To build our application, we use the following multi-stage Dockerfile
which we’ll add to the root of the repository:
FROM elixir:1.17-alpine AS build
WORKDIR /app
# Install build dependencies
RUN mix local.hex --force && mix local.rebar --force
# Copy the mix files and install dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get
# Copy the source code and compile the application
COPY lib ./lib
RUN MIX_ENV=prod mix release
FROM elixir:1.17-alpine
# Set the user and group to nobody:nobody (in numeric)
USER 65534:65534
WORKDIR /app
ENV HOME=/app
# Configure the writable directories
ENV ERL_COOKIE_PATH=/data/.erlang.cookie
ENV ERL_CRASH_DUMP=/data/crash.dump
# Copy the compiled app
COPY --from=build /app/_build/prod/rel/example .
EXPOSE 3000
CMD ["/app/bin/example", "start"]
The build
stage will setup the dependencies needed to compile your application and ultimately produce a release in /workspace/_build/prod/rel/example
. The next (and final) stage copies the results and runs it.
/data
.Create an application on Runway:
$ runway app create
INFO checking login status
INFO created app "assistant-scientist"
create app assistant-scientist: done
next steps:
* commit your changes
* runway app deploy
* runway open
And deploy:
$ runway app deploy && runway open
Congrats, you’re Elixir app is now on Runway! 👊👊👊