Reading a File in Elixir on Heroku

Last reviewed on February 03, 2019

For the past 10 years, my programming experience has mostly focussed on JavaScript and PHP. I've learned a few functional programming concepts through JavaScript, such as function purity, immutability, and side effects, but I've never worked with a true functional language. To learn more about functional programming, I've decided to learn Elixir and write about what I learn to help me learn.


Recently I created a small API in Elixir/Phoenix and deployed it to Heroku using the free plan. The API read an HTML file stored in a data folder located in the project root.

In one of my controllers, I had the following:

def index(conn, %{"course" => course, "year" => year}) do
  Path.expand("./../../../data/#{year}", __DIR__)
  |> Path.join("#{course}.html")
  |> log
  |> File.read
  |> handle_file(conn, course, year)
end

Locally, the path that was logged was /Users/david/Sites/students/data/2019/itp405.html. Everything worked.

Once I pushed it up to Heroku, I noticed that the file couldn't be read. After inspecting the logs on Heroku (using heroku logs --app davids-api), I found that the path that was generated from Path.expand was /tmp/build_b2f02984e1fe996483c2e4e14d6e337d/data/2019/itp405.html. For whatever reason, that path wasn't working and resulted in a "file not found" error.

Eventually I discovered that the file could be read using the path /app/data/2019/itp405.html. My final solution turned out to be:

def index(conn, %{"course" => course, "year" => year}) do
  get_path(course, year)
  |> log
  |> File.read
  |> handle_file(conn, course, year)
end

defp get_path(course, year) do
  if System.get_env("HEROKU_EXEC_URL") do
    "/app/data/#{year}/#{course}.html"
  else
    Path.expand("./../../../data/#{year}", __DIR__)
    |> Path.join("#{course}.html")
  end
end

It turns out there is an environment variable named HEROKU_EXEC_URL when running on Heroku. I ended up using that to conditionally determine the file path. This isn't an ideal solution, but it got my app working. If you know of a better way, please let me know in the comments!

UPDATE (2/3/2019)

It turns out, there is a better way to do this, as David made me aware of in the comments section. Instead of putting the data folder in the root of my project, I moved it into the priv directory. Then, I used code:priv_dir from Erlang to get the path to the priv directory:

defp get_path(course, year) do
  "#{:code.priv_dir(:students)}/data/#{year}/"
  |> Path.join("#{course}.html")
end

This function takes the application name, which you can find in the project function in mix.exs.

No more conditional logic based on a Heroku environment variable!