RicoTrevisan

RicoTrevisan

PhoenixAnalytics: Permission denied error when accessing DuckDB file in Docker/Coolify deployment

I’m trying to add PhoenixAnalytics by @lalabuy948 to a Phoenix project. I got it to work locally, but I can’t make it work on my Hetzner server. My guess is that I’ve got to change my Dockerfile but I’ve gone back and forth with the LLMs and I got nowhere. Hoping anyone can point me to some more troubleshooting ideas.

Let me start from the end.
In the application logs, here’s the error:

05:45:14.329 request_id=F__EY628KPIaoG4AAABD [info] GET /api/v1/
05:45:14.329 request_id=F__EY628KPIaoG4AAABD [info] Sent 200 in 481µs
05:45:15.206 [error] GenServer PhoenixAnalytics.Services.Batcher terminating
** (MatchError) no match of right hand side value: {:error, "Table 'requests' could not be found"}
    (phoenix_analytics 0.2.0) lib/phoenix_analytics/repo.ex:215: PhoenixAnalytics.Repo.insert_many/1
    (phoenix_analytics 0.2.0) lib/phoenix_analytics/services/batcher.ex:101: PhoenixAnalytics.Services.Batcher.handle_info/2
    (stdlib 6.0) gen_server.erl:2173: :gen_server.try_handle_info/3
    (stdlib 6.0) gen_server.erl:2261: :gen_server.handle_msg/6
    (stdlib 6.0) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: :check_batch

Coolify setup

Persistent storage

Host Server

I can see that directory if I hop on the host server

inside the Docker container

Environment Variables

I add DUCKDB_PATH as an env variable

/app/data/analytics.duckdb

Elixir setup

config.exs

Here’s the bit I added

config :phoenix_analytics,
  duckdb_path: System.get_env("DUCKDB_PATH") || "/app/data/analytics.duckdb",
  app_domain: System.get_env("PHX_HOST") || "reabra.com.br"

endpoint.ex

I add the following

  plug Plug.Static,
    at: "/",
    from: :reabra,
    gzip: false,
    only: ReabraWeb.static_paths()

  plug PhoenixAnalytics.Plugs.RequestTracker

...

router.ex

...
  use PhoenixAnalytics.Web, :router
...
  scope "/admin", ReabraWeb do
    pipe_through [:browser, :require_authenticated_user, :require_admin]
    phoenix_analytics_dashboard("/analytics")

...

Dockerfile

Here’s my Dockerfile

ARG ELIXIR_VERSION=1.17.2
ARG OTP_VERSION=27.0
ARG DEBIAN_VERSION=bullseye-20240701-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}
ARG DUCKDB_PATH

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

# Install DuckDB
RUN wget https://github.com/duckdb/duckdb/releases/download/v1.1.2/duckdb_cli-linux-amd64.zip \
    && unzip duckdb_cli-linux-amd64.zip \
    && mv duckdb /usr/local/bin/ \
    && rm duckdb_cli-linux-amd64.zip

# 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
ENV DUCKDB_PATH=${DUCKDB_PATH}

WORKDIR "/app"
RUN mkdir -p /app/data && chown -R nobody:root /app/data
RUN touch /app/data/analytics.duckdb && chown nobody:root /app/data/analytics.duckdb
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/reabra ./

USER nobody

# If using an environment that doesn't automatically reap zombie processes, it is
# advised to add an init process such as tini via `apt-get install`
# above and adding an entrypoint. See https://github.com/krallin/tini for details
# ENTRYPOINT ["/tini", "--"]

# CMD ["/app/bin/server"]
CMD ["sh", "-c", "/app/bin/migrate && /app/bin/server"]

Migration

I added the necessary migration

defmodule Reabra.Repo.Migrations.AddPhoenixAnalytics do
  use Ecto.Migration

  def up, do: PhoenixAnalytics.Migration.up()
  def down, do: PhoenixAnalytics.Migration.down()
end


FYI: There’s a nice walkthrough / demo here:

Most Liked

ruslandoga

ruslandoga

It might be a bit off-topic, but I had a similar problem with SQLite in the past. For me it was that the owner of the bind mount was different from UID in the container. I think this is also the problem here as /app/data seems to be owned by 1001 (probably host’s reabra) and not nobody.

Also I think bind mounts would replace the contents of the container image. So

RUN mkdir -p /app/data && chown -R nobody:root /app/data
RUN touch /app/data/analytics.duckdb && chown nobody:root /app/data/analytics.duckdb

might be deleted and replaced with the hosts /data/coolify/…/…/data contents (which is owned by 1001 and not nobody).

I know of two easy fixes:

  • allow hosts’s /data/coolify/…/…/data to be read and written by UID of the container
  • or use volumes :slight_smile:
lalabuy948

lalabuy948

Hi @RicoTrevisan, it looks like you didn’t run migration.
As if PhoenixAnalytics would not have access file to write it would not start at all.

You can try to run it manually:

iex -S mix 
PhoenixAnalytics.Migration.up()

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New

Other popular topics Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New

We're in Beta

About us Mission Statement