Make System.cmd() Call From Docker Container

I need to call out to ffmpeg and ffprobe to process a file that I upload(Phoenix 1.6 app). Works fine in development environment but when I try to call System.cmd() from the running container in production the result is :enoent

I’m pretty sure this is because the docker container does not have the binaries available to it(I just put the in the root project dir in the development environment).

How do I make these available to the container? I presume the correct permissions should be set also?

Well, you can append the project root to the PATH inside the container?

Best way is probably to copy the required binaries to the right location in your image when you build the image using the Dockerfile COPY command.

How are you building the image?

Alternatively, if the binaries you’re trying to call can be installed using the package manager of the distribution your image is based on, that should be even better. So something like:

apk add my_binary_package

in your Dockerfile, if you’re using an Alpine-based image

Yes I’m using an alpine based image and I’m almost there with your suggestion to apk add them:

here is the first portion of my Dockerfile(link to full Dockerfile below):

# STEP 1 - BUILD RELEASE 
# cannot use alpine 3.14.0 because of issue here with bcrypt-elxir compiling using make on docker:
# https://github.com/riverrun/bcrypt_elixir/issues/26#issuecomment-881966412
FROM hexpm/elixir:1.12.3-erlang-24.0.6-alpine-3.13.5 AS build

# install build dependencies
RUN apk add --update git \
    build-base \
    ffmpeg \
    --update npm

RUN apk add ffprobe --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community

I can fetch ffmpeg OK:

RUN apk add --update git \
    build-base \
    ffmpeg \
    --update npm

but putting ffprobe \ in the above command results in:
ffprobe (no such package):

I also tried:

RUN apk add ffprobe --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community

but same error.

I can try copying over the ffprobe that I have in the second build phase but how do I know what binary to download that will run on alpine? I am aware of this page: FFbinaries - Download binaries for ffmpeg, ffprobe, ffserver and ffplay (cross-platform: Windows, Mac, Linux)

I noticed that the owner and group are nobody, which appears to “smell” to me:

bash-5.1$ ls -ls
total 72856
     8 drwxr-xr-x    1 nobody   nobody        4096 Jan  6 19:10 bin
     4 -rwxr-xr-x    1 nobody   nobody         602 Oct 18 03:02 entrypoint.sh
     8 drwxr-xr-x    1 nobody   nobody        4096 Jan  6 19:10 erts-12.0.4
 72816 -rwxr-xr-x    1 nobody   nobody    74563488 Sep  7  2019 ffprobe
     8 drwxr-xr-x    1 nobody   nobody        4096 Jan  6 19:10 lib
     8 drwxr-xr-x    1 nobody   nobody        4096 Jan  6 19:10 releases
     4 drwxr-xr-x    2 nobody   nobody        4096 Jan  6 19:12 tmp

Full Dockerfile:

Not familiar with ffmpeg/ffprobe, but according to this:

https://pkgs.alpinelinux.org/contents?branch=edge&name=ffmpeg&arch=x86&repo=community

it looks like ffprobe is part of the ffmpeg package.

In fact, I just installed the ffmpeg package in a local alpine container and I was able to run both binaries (ffmpeg and ffprobe). Give it a try.

1 Like

Yeah you’re right. However despite that System.cmd() just doesn’t see it. I put it a trivial call to ffmpeg from mount() home page:

    ffmpeg = System.cmd("ffmpeg", ["-v"], [stderr_to_stdout: true])
    IO.inspect(ffmpeg, label: "ffmpeg output")
faithful_word    | Server: localhost:4000 (http)
faithful_word    | Request: GET /
faithful_word    | ** (exit) an exception was raised:
faithful_word    |     ** (ErlangError) Erlang error: :enoent
faithful_word    |         (elixir 1.12.3) lib/system.ex:1041: System.cmd("ffmpeg", ["-v"], [stderr_to_stdout: true])
faithful_word    |         (faithful_word 0.1.0) lib/faithful_word_web/live/landing_live/landing_page.ex:95: FaithfulWordWeb.Pages.Landing."mount (overridable 1)"/3

I don’t get it heh

Probably because it can’t find the executable in the PATH env variable.

Try to pass it the absolute path to the executable:

System.cmd("/usr/bin/ffmpeg", ...)

1 Like

Yeah good point. getting a similar error:

faithful_word    | Server: localhost:4000 (http)
faithful_word    | Request: GET /
faithful_word    | ** (exit) an exception was raised:
faithful_word    |     ** (ErlangError) Erlang error: :enoent
faithful_word    |         :erlang.open_port({:spawn_executable, '/usr/bin/ffmpeg'}, [:stderr_to_stdout, :use_stdio, :exit_status, :binary, :hide, {:args, ["-v"]}])
faithful_word    |         (elixir 1.12.3) lib/system.ex:1052: System.do_cmd/3

When I docker exec -ti faithful_word bash into the running container I don’t see the ffmpeg binary:

bash-5.1$ pwd
/usr/bin
bash-5.1$ ls
[               cmp             du              getconf         logger          nproc           pgrep           seq             tac             uniq            wc
[[              comm            eject           getent          lsof            nsenter         pkill           setkeycodes     tail            unix2dos        wget
awk             cpio            env             groups          lsusb           nslookup        pmap            setsid          tee             unlink          which
basename        createdb        expand          hd              lzcat           od              printf          sha1sum         test            unlzma          whoami
bc              createuser      expr            head            lzma            openssl         pscan           sha256sum       time            unlzop          whois
beep            crontab         factor          hexdump         lzopcat         openvt          psql            sha3sum         timeout         unshare         xargs
blkdiscard      cryptpw         fallocate       hostid          md5sum          passwd          pstree          sha512sum       top             unxz            xxd
bunzip2         cut             find            iconv           mesg            paste           pwdx            showkey         tr              unzip           xzcat
bzcat           dc              flock           id              microcom        pg_basebackup   readlink        shred           traceroute      uptime          yes
bzip2           deallocvt       fold            install         mkfifo          pg_dump         realpath        shuf            traceroute6     uudecode
cal             diff            free            ipcrm           mkpasswd        pg_dumpall      reindexdb       sort            truncate        uuencode
chvt            dirname         fuser           ipcs            nc              pg_isready      renice          split           tty             vacuumdb
cksum           dos2unix        gdbm_dump       killall         nl              pg_receivewal   reset           ssl_client      ttysize         vi
clear           dropdb          gdbm_load       ldd             nmeter          pg_recvlogical  resize          strings         udhcpc6         vlock
clusterdb       dropuser        gdbmtool        less            nohup           pg_restore      scanelf         sum             unexpand        volname

which explains why I get :enoent but I wonder why the binary is not there.

OK I got it! The issue was that I was adding the ffmpeg binary during the build phase and not the runtime dependency phase(naturally). This fixed it:

####################################################################################################
# STEP 2 - FINAL
FROM alpine:3.13.5 as app
# install runtime dependencies
# https://stackoverflow.com/questions/68010688/docker-run-error-loading-shared-library-libstdc-so-6-and-libgcc-s-so-1
RUN apk upgrade --no-cache && \ 
    apk add --update bash \
    openssl \ 
    ffmpeg \
    postgresql-client \
    libstdc++

Thanks for your help!

1 Like