Local Liveview Uploads in production

Hello everyone,

I’m trying to deploy an app that uses Liveview uploads to save to both a local /uploads folder as well a S3 bucket. I guess I just wanted to test how both work! Anyway, the upload to S3 is working in production but I’m getting 404 on images after attempting to upload locally. I am deploying with Releases and when I build with images uploaded in dev, they are available in the local /upload folder. But I soon as I try to upload a new image while running the build I get a 404. Any ideas? What am I missing?

Thanks for your help in advance!

How are you referencing the “local /uploads” folder in dev & prod environments?

Hey Sfusato,

the uploads folder is being referenced in the endpoint.ex together with the other static assets for both prod and dev:

 plug Plug.Static,
    at: "/",
    from: :labs,
    gzip: false,
    only: ~w(css fonts images js favicon.ico robots.txt uploads)

Do I need to a specific configuration for production?

What I meant is how are you passing the path to the location where to store the file?

Let’s say you are using File.cp(upload_source_file, destination_path) to store the file. I was asking about the value of destination_path.

Ah, sorry I miss understood you. I’m storing the images to priv/static/uploads

consume_uploaded_entries(socket, :avatar, fn meta, entry ->
dest = Path.join("priv/static/uploads", "#{entry.uuid}.#{ext(entry)}")
 File.cp!(meta.path, dest)
 end)

In the context of the local dev environment this works great, since the application is running from the project’s root folder. In prod, however, your application is running from within _build/prod/rel/..., thus why the file isn’t copied where you intended to when you pass a relative path.

Now, the uploads directory is pretty much a config concern, meaning I would personally prefer to have it in a config file rather than in a module’s function or a module’s attribute. For prod, pass the absolute path to it.

Example:

# dev.exs
config :labs, upload_path: "priv/static/uploads"
# prod.exs
config :labs, upload_path: "/var/www/labs/priv/static/uploads"

Retrieve it with Application.fetch_env!(:labs, :upload_path)

6 Likes

As already mentioned the priv directory is not always in the same place relative to the cwd. Use Application.app_dir(:my_app, "priv/static/uploads") instead, which will work in any environment.

6 Likes

Amazing, guys. Thank you so much for the explanation.

I just followed the instructions above, specifically replacing "priv/static/uploads" with Application.app_dir(:my_app, "priv/static/uploads"), and I still receive the error:

** (File.CopyError) could not copy from "/tmp/plug-1618/live_view_upload-1618951129-355143682828838-1" to "/home/runner/work/MyApp/MyApp/_build/test/lib/my_app/priv/static/uploads/ebeab0e5-115e-4ace-a6a7-776877c325f4.png": no such file or directory

I am running the code via GitHub Actions Elixir CI.

consume_uploaded_entries(socket, :avatar, fn meta, entry ->
  uploads_dir = Application.app_dir(:my_app, "priv/static/uploads")
  dest = Path.join(uploads_dir, filename(entry))
  File.cp!(meta.path, dest)
  Routes.static_path(socket, "/uploads/#{filename(entry)}")
end)

Did you make sure the folder exists before trying to copy into it?

Yes. The functionality is working fine in development. But then I wrote a unit test for the function, and it passes in my local test environment, but when I push the commit through CI (Github Actions), that test fails due to the aforementioned File.CopyError.

Still the error seems to suggest at least some part in the path of /home/runner/work/MyApp/MyApp/_build/test/lib/my_app/priv/static/uploads/ebeab0e5-115e-4ace-a6a7-776877c325f4.png does not exist. You might have mantually created the uploads directory in development, but in CI it’s not present.

2 Likes

Not sure why it’s not there… When I check _build/test/ on my local machine, lib/my_app/priv/static/uploads is there alright, and I know I didn’t create that manually. If it’s missing, how can I ensure the folder is being created in the build?

In the :dev and :test environments, the priv dir is a symlink to the /priv dir at the root of your app.

/priv/static/ is included in the .gitignore for a Phoenix app by default, so if I had to guess your priv directory likely exists in prod, but the sub-directories do not.

Somewhere in your production build you can run mkdir -p /path/to/my/app/priv/static/uploads to ensure the directory exists.

PS– With the latest release of LiveView we updated the Uploads guides to use :code.priv_dir(:my_app) when building the path (though I probably would have used Application.app_dir if I had thought about it at the time).

5 Likes

Thank you! I simply added a step in the CI workflow to run mkdir before running the test. Now the test is successful. :smile:

1 Like