Saved images not displaying on dev machine using filesystem

Locally all my images are 404 not found (haven’t tried this in production). This is a brand new implementation so I’m sure I got something mixed up along the way.

I’m using ex_aws to store images in production, but locally I am just using the filesystem. I use system configs to determine what gets uploaded where.


config :app, App.Images.PostImages,
  provider: App.Storage.LocalProvider,
  root: Path.expand(Path.join(__DIR__, "../priv/static/images/posts")),
  url_prefix: "/pics"


  @doc """
  This is what fetches a URL of an uploaded image
  @impl true
  def url(%{url_prefix: url_prefix}, key, _opts \\ []) do
    with :ok <- validate_key(key) do
      ok({:local, Path.join(url_prefix, key)})
      res -> {:error, res}


 image_file_storage = Application.get_env(:app, App.Images.PostImages)

  if Keyword.fetch!(image_file_storage, :provider) == App.Storage.LocalProvider do
    plug Plug.Static,
      at: "/pics",
      from: Keyword.fetch!(image_file_storage, :root),
      gzip: false

The url/3 function returns a url /pics/:image_key, which it does. And I can view the image if I navigate to ../priv/static/images/posts/:image_key but Phoenix is not finding localhost:4000/pics/:image_key. What am I missing? I’ve implemented this in other systems before, but this is the first time I’ve written all provider behaviours myself.

1 Like

If the code blocks were copied directly from the app, it looks like inconsistency referencing of modules where some are prefixed with App and others Ciao.

1 Like

Sorry, that was a typo. Ciao is the name of the app, but for this post I was trying to make it more generic. It’s actually Ciao everywhere.

Gotcha, just a sanity check :slight_smile:

Looking at the Plug.Static module docs,

The preferred form is to use :from with an atom or tuple, since it will make your application independent from the starting directory. For example, if you pass:

plug Plug.Static, from: "priv/app/path"

Plug.Static will be unable to serve assets if you build releases or if you change the current directory. Instead do:

plug Plug.Static, from: {:app_name, "priv/app/path"}

Have you tried it with the preferred form?

I just gave it a shot. No luck. I’ll be working on this tonight until I either figure it out or my wife gets home so I’ll post back if I figure anything else out.

Oddly I added an inspect into the endpoint.ex

  if Keyword.fetch!(image_file_storage, :provider) == Ciao.Storage.LocalProvider do
    IO.inspect(!(image_file_storage, :root)), label: "Files?")

    plug Plug.Static,
      at: "/pics",
      from: {:ciao, Keyword.fetch!(image_file_storage, :root)},
      gzip: false

It’s showing the file in there, so the path I’m passing to from is a good path.

One other thing I read, the Plug.Static should be before: plug(CiaoWeb.Router) I moved that around still no luck.

No foruther progress on this, this is the stacktrace anytime I try to access one of the image assets:

[debug] ** (Phoenix.Router.NoRouteError) no route found for GET /pics/57c02d9f-27fd-4178-a5d8-012935bb2228 (CiaoWeb.Router)
    (ciao 0.1.0) lib/phoenix/router.ex:405:
    (ciao 0.1.0) lib/ciao_web/endpoint.ex:1: CiaoWeb.Endpoint.plug_builder_call/2
    (ciao 0.1.0) lib/plug/debugger.ex:136: CiaoWeb.Endpoint."call (overridable 3)"/2
    (ciao 0.1.0) lib/ciao_web/endpoint.ex:1:
    (phoenix 1.6.15) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
    (cowboy 2.9.0) /Users/travis/Documents/ciao_place/phoenix/ciao/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
    (cowboy 2.9.0) /Users/travis/Documents/ciao_place/phoenix/ciao/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
    (cowboy 2.9.0) /Users/travis/Documents/ciao_place/phoenix/ciao/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
    (stdlib 3.11) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Hmm, did you try a relative path from app directory for the :root config value after changing to the preferred form for :from?

root: "priv/static/images/posts",

Or maybe even hardcoding it in just for now like so:
from: {:ciao, "priv/static/images/posts"},

1 Like

Ah! That did it. Interesting, in the dev.exs if I change root: Path.expand(Path.join(__DIR__, "/priv/static/images/posts")) to: root: "priv/static/images/posts" I get an error:

== Compilation error in file lib/ciao/images/post_images.ex ==
** (File.Error) could not make directory (with -p) "/priv/static/images/posts": no such file or directory
    (elixir 1.13.1) lib/file.ex:316: File.mkdir_p!/1
    lib/ciao/images/storage/local_provider.ex:13: Ciao.Storage.LocalProvider.init/1
    lib/ciao/images/post_images.ex:12: (module)

So now I have two variables, root and relative:

config :ciao, Ciao.Images.PostImages,
  provider: Ciao.Storage.LocalProvider,
  root: Path.expand(Path.join(__DIR__, "/priv/static/images/posts")),
  relative: "priv/static/images/posts",
  url_prefix: "/pics"

I find that to be a bit frustrating, but this is a side project so I’m happy to leave it as is for now. Thank you so much for your help!

1 Like