Function Bcrypt.Base.gensalt_nif/3 is undefined (module Bcrypt.Base is not available)

I’m running Phoenix 1.3 with Elixir 1.6 and Erlang OTP 20 in a docker container.
I use comeonin ~> 4.0 and bcrypt_elixir ~> 1.0

When I try to call the function Comeonin.Bcrypt.hashpwsalt(WITH_MY_PASSWORD), the error returned is: function Bcrypt.Base.gensalt_nif/3 is undefined (module Bcrypt.Base is not available)

I’ve read other forums and they all point to the fact that Bcrypt 1.0 is only compatible with erlang 20. So I don’t really know what the issue is here.

Are you able to share your Dockerfile and your mix.exs (the deps part at least)?

Here is the dockerfile for my phoenix app
# base image elixer to start with
FROM elixir:1.6

# install hex package manager
RUN mix local.hex --force
RUN mix local.rebar --force

# install the latest phoenix 
RUN mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez --force

# create app folder
RUN mkdir /app
COPY ./my_app /app
WORKDIR /app

# install dependencies
RUN mix deps.get && (cd deps/bcrypt_elixir && make clean && make) && mix deps.compile
WORKDIR /app

# run phoenix in *dev* mode on port 4000
CMD mix ecto.create && mix ecto.migrate && mix run priv/repo/seeds.exs && mix phx.server

Why do you cd into deps/bcrypt_elixir and run make clean && make there? That feels wrong…

My assumption is, that you do this, because the deps folder from your host is put into your docker container during COPY ./my_app /app and therefore you have build artifacts which do not match the system in the container and you try to “repair” this by force rebuilding in docker? This probably doesn’t work, as there might still old artifacts for the wrong architecture remain in the also copyed _build folder.

Instead you should properly .dockerignore the deps and _build folders to really be sure, that nothing wrong slips in.

this is the reason why i did it:

if I ignore my_app/deps, does that mean that i can’t run mix deps.get in my dockerfile anymore? What do i do then?

No, it actually means that you have to do it. Without ignoring it, the deps and _build folder from your host will be copied over into the container, wasting space in the layer is one of the downsides, the other is, that they might contain artifacts that are incompatible with the containerized erlang and elixir.

Also the linked issue does not affect a docker container, at least not if it is build correctly. As a correctly built docker container should never see old artifacts in deps and _build as they never should exist on the container prior to mix deps.get and mix deps.compile/mix compile.

The ticket above really only affects those that compiled a NIF on OTP N, update erlang and get back to that project, but now have OTP N+1. Then the NIF wouldn’t be recognized, and in that issue a nasty workaround was shown. The proper way to handle this is actually mix do deps.clean bcrypt_elixir, deps.build.

But to be honest, my general advise is to simply rm -rf deps _build; mix do deps.get, deps.compile after updating Erlang or Elxir. Just to make sure that all libraries used use the latest enhencements of the language.

I don’t understand, I removed both deps and _build folders. Does the command RUN mix deps.get && mix deps.compile get included in the dockerfile or not. When I run docker-compose build then docker-compose up, Because I put deps and _build in the dockerignore file I get this error:

phoenix_1  | Unchecked dependencies for environment dev:
phoenix_1  | * bcrypt_elixir (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * faker (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * ueberauth_google (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * redix_pubsub (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * gettext (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * absinthe (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * ueberauth (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * poolboy (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * ueberauth_facebook (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * poison (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * comeonin (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * guardian (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * cowboy (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * httpoison (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * absinthe_ecto (https://github.com/absinthe-graphql/absinthe_ecto.git)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * ecto (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * phoenix_pubsub (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * absinthe_plug (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * phoenix (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * postgrex (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | * phoenix_ecto (Hex package)
phoenix_1  |   the dependency is not available, run "mix deps.get"
phoenix_1  | ** (Mix) Can't continue due to errors on dependencies

Or do I run mix deps.get and mix deps.compile in the terminal first before running docker-compose build and then subsequently docker-compose up?

Either way, I still get the same error and it doesn’t work

Here is my repository you can check it out and see what I’m doing wrong, I have two dockerfiles since I want to run a golang microservice along with my web server:
https://github.com/sc4224/genesys

Of course it does, it is needed to fetch and compile the dependencies.

ok so i still get the error saying that the dependency is not available

Which dependency is not available? When do you get that error message? Do you get any errors or messages during building container?

The errors are the errors mentioned above. This happens when I run docker-compose build. Take a look at my repository above, is it the fact that I am putting too many services in one container, possibly giving rise to errors when I reference folder paths?

When I build your container, this is what I see:

Step 8/10 : RUN mix deps.get
 ---> Running in 38022be2ebe1
** (Mix.Config.LoadError) could not load config config/config.exs
    ** (SyntaxError) config/config.exs:24: syntax error before: config
    (elixir) lib/code.ex:192: Code.eval_string/3
    (mix) lib/mix/config.ex:188: Mix.Config.read!/2
    (mix) lib/mix/tasks/loadconfig.ex:37: Mix.Tasks.Loadconfig.load/1
    (mix) lib/mix/tasks/loadconfig.ex:27: Mix.Tasks.Loadconfig.run/1
    (mix) lib/mix/task.ex:314: Mix.Task.run_task/3
    (mix) lib/mix/cli.ex:79: Mix.CLI.run_task/2

So my assumption is, that you happily changed around in your Dockerfile, but never rebuilt the container.

If I hardcode the secret key for Guardian in config.exs, this is not an error, so no, this has nothing to do with the dockerfile. I don’t know what is it I changed, nothing has changed in my dockerfile at all

The trailing , is an error. If the compiler tells you that there is an error, then you can’t argue!

I removed that trailing comma, rebuilt the container, docker run -it genesis iex -S mix into it, ignored a bunch of error messages related to unavailable dependencies, got into iex and did this:

iex(1)> Comeonin.Bcrypt.hashpwsalt("Foo")
"$2b$12$ZvbuL.cQ.8RklUhAjBO2../NXRJ4heFp2lPafeX2Ue1DrkEbUfwAG"

So: Works on my machine

So after I removed that comma, everything builds, and I was able to do docker-compose up. Now the problem still remains:

phoenix_1  |     ** (UndefinedFunctionError) function Bcrypt.Base.gensalt_nif/3 is undefined (module Bcrypt.Base is not available)
phoenix_1  |         (bcrypt_elixir) Bcrypt.Base.gensalt_nif([218, 167, 96, 212, 228, 19, 3, 128, 160, 226, 183, 148, 194, 74, 214, 34], 12, 98)
phoenix_1  |         (bcrypt_elixir) lib/bcrypt.ex:52: Bcrypt.gen_salt/2
phoenix_1  |         (bcrypt_elixir) lib/bcrypt.ex:88: Bcrypt.hash_pwd_salt/2
phoenix_1  |         (my_app) lib/my_app/accounts/user.ex:63: MyApp.Accounts.User.put_pass_hash/1
phoenix_1  |         (my_app) lib/my_app/accounts/accounts.ex:74: MyApp.Accounts.update_user/2

This happens here in this function:

  defp put_pass_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} ->
        IO.puts(password)

        put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(password))

      _ ->
        changeset
    end
  end

I don’t know why this keeps happening, where is gensalt getting called?

I did as instructed, deleted deps, deleted _build, in my .dockerignore file I specified to ignore ./my_app/deps and ./my_app/_build

Please do docker system prune to delete all containers that are not currently running or referenced by running containers (before that, make sure using docker ps and docker stop that no containers related to your application are running).

Then start from scratch using docker-compose build, after that has finished, try again to start your containers as you are used to it.

As it works for me after a pristine clone of your application, I have to assume, that there is something weird in the caches failing your builds.

I took a closer look again, this time into the docker compose file. You are using a volume, pulling all your local stuff into the container shadowing the stuff in the container. This won’t work. Please remove the volume.

1 Like

So I tried docker run -it genesys_phoenix iex -S mix and i see the errors but I ignored them and ran Comeonin.Bcrypt.hashpwsalt("Foo") in the shell. It works. But I have no idea why it doesn’t work in the application when I call the function in the user context

Just remove the volume and run docker-compose up again?

Ok this works, now my question is probably a fundamental one, but I also never understood. What am I actually doing with volumes in this case. Was this a bind mount or a named volume. From what I understand, it is a bind mount. But I don’t understand when I should and shouldn’t use it