System.cmd() works locally, but not in production for some reason

Hello,

I have a function that is part of image validation pipeline which is causing me issue for some reason…

# example of a temporary filepath from user upload
filepath = "/tmp/plug-1679/multipart-1679569576-340151210667-1"], [])"

Inside my function I do the following

{output, 0} = System.cmd("file", ["--mime", "--brief", filepath]) 

This works just fine locally but in production, I’m getting the following error

** (exit) an exception was raised:
** (ErlangError) Erlang error: :enoent
 (elixir 1.14.3) lib/system.ex:1060: System.cmd("file", ["--mime", "--brief", "/tmp/plug-1679/multipart-1679569576-340151210667-1"], [])

From the System.cmd() docs

:enoent - the command does not point to an existing file

Checking if file exist returns true

File.exists?(filepath) #=> true

Can anyone point me in the right direction here? As I said, it works just fine locally so I’m not sure what could be causing this issue.

Could it be that the file cmd doesn’t exist in your prod environment or that you should pass the whole path for said command?

2 Likes

I don’t know to be honest… how would I add it if it doesn’t exist?

I have tmp folder in the root so this should be the whole path.

It should be there, if it’s not installing it will depend on your distro. If it is there try passing the full path to the file command (eg /usr/bin/file).

1 Like

That path also doesn’t work…

I also creating a test file and tried to System.cmd() it with iex

System.cmd("file", ["--mime", "--brief", "/tmp/test.txt"])

the same error shows again

** (ErlangError) Erlang error: :enoent
    (elixir 1.14.3) lib/system.ex:1060: System.cmd("file", ["--mime", "--brief", "tmp/test.txt"], [])
    iex:1: (file)

It was a possible location for the file command, you could start by trying to issue the command from your prod terminal (if you have access to it or ask your sysadmin), if it works then you need to find out where the command is located (eg using whereis).

The :enoent is almost certainly related to the file Command, otherwise the command would return a non 0 result.

2 Likes

Try checking the result of System.find_executable in production. If it returns nil, then there is no file executable.

4 Likes

Yup, it’s nil.

I tried adding file to my Dockerfile but it’s still returning nil, I don’t know why, maybe Fly.io is blocking it or something.

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential file git \
    && apt-get clean && rm -f /var/lib/apt/lists/*_*

What I’m trying to get here is the file content type like image/jpeg, is there another way to get it from a file except with System.cmd()?

Could you, by any chance, share your Dockerfile (redacting sensitive data if necessary)?

Edit, if I remember correctly, the Docker images in Phoenix docs are built in two steps : one for building the release, and another for building the release image, from your line you seem to be adding file to the first step (because build-essentials is only required for the first step). I might be wrong here.

I’m not aware of a library that checks the mimetype of a file based on its magic number.

2 Likes

The moment I copied the Dockerfile here, I saw the problem… I was adding file to wrong line…

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential file git \
    && apt-get clean && rm -f /var/lib/apt/lists/*_*

when I had to add it to this line

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales file \
  && apt-get clean && rm -f /var/lib/apt/lists/*_*

Here’s the full docker file

# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
# instead of Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=tags
#
# This file is based on these images:
#
#   - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
#   - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20221004-slim - for the release image
#   - https://pkgs.org/ - resource for finding needed packages
#   - Ex: hexpm/elixir:1.14.3-erlang-25.2-debian-bullseye-20221004-slim
#
ARG ELIXIR_VERSION=1.14.3
ARG OTP_VERSION=25.2
ARG DEBIAN_VERSION=bullseye-20221004-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

FROM ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
    && apt-get clean && rm -f /var/lib/apt/lists/*_*

# prepare build dir
WORKDIR /app

# install hex + rebar
RUN mix local.hex --force && \
    mix local.rebar --force

# set build ENV
ENV MIX_ENV="prod"

# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

COPY lib lib

COPY assets assets

# compile assets
RUN mix assets.deploy

# Compile the release
RUN mix compile

# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/

COPY rel rel
RUN mix release

# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales file \
  && apt-get clean && rm -f /var/lib/apt/lists/*_*

# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR "/app"
RUN chown nobody /app

# set runner ENV
ENV MIX_ENV="prod"

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/helloworld ./

USER nobody

CMD ["/app/bin/server"]

# Appended by flyctl
ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"

Everything works now, thank you @krstfk and @josevalim for checking it out and helping me solve it.

Glad to know you’ve fixed it.

1 Like