web-dev-qa-db-fra.com

Comment exécuter Elixir Phoenix Docker en mode production en utilisant l'exemple des guides Phoenix?

L'exemple Dockerfile donné sur les guides Elixir Phoenix semble obsolète et cassé. L'exemple se trouve ici: https://hexdocs.pm/phoenix/releases.html#containers

J'ai créé une application Vanilla comme ça: mix phx.new hello_world

Le Dockerfile cassé:

# FROM elixir:1.9.0-Alpine as build

# install build dependencies
RUN apk add --update git build-base nodejs yarn python

# prepare build dir
RUN mkdir /app
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 ./
COPY config config
RUN mix deps.get
RUN mix deps.compile

# build assets
COPY assets assets
RUN cd assets && npm install && npm run deploy
RUN mix phx.digest

# build project
COPY priv priv
COPY lib lib
RUN mix compile

# build release
COPY rel rel
RUN mix release

# prepare release image
FROM Alpine:3.9 AS app
RUN apk add --update bash openssl

RUN mkdir /app
WORKDIR /app

COPY --from=build /app/_build/prod/rel/my_app ./
RUN chown -R nobody: /app
USER nobody

ENV HOME=/app

J'essaie d'exécuter le Dockerfile à partir d'une installation d'application Vanilla Phoenix et j'ai rencontré de nombreux problèmes, notamment:

# FROM elixir:1.9.0-Alpine as build
# Needs to be uncommented

RUN cd assets && npm install && npm run deploy
# npm install failed, had to add nodejs-npm

COPY rel rel
# errors here, there is no rel, should I remove?

More errors later because DATABASE_URL and SECRET_KEY_BASE are not declared

Je ne suis pas un expert et jusqu'à présent, le Dockerfile ressemble maintenant à ceci:

FROM elixir:1.9.1-Alpine as build

# install build dependencies
# modified: is this correct?
RUN apk add --update git build-base nodejs nodejs-npm yarn python

# prepare build dir
RUN mkdir /app
WORKDIR /app

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

# set build ENV
# modified: is this correct?
ENV DATABASE_URL=${DATABASE_URL} \
  SECRET_KEY_BASE=${SECRET_KEY_BASE} \
  MIX_ENV=prod

# install mix dependencies
COPY mix.exs mix.lock ./
COPY config config
RUN mix deps.get
RUN mix deps.compile

# build assets
COPY assets assets
RUN cd assets && npm install && npm run deploy

# build project
COPY priv priv
COPY lib lib
RUN mix compile

# build release
# removed next line, is that correct?
# COPY rel rel
RUN mix release

# prepare release image
FROM Alpine:3.9 AS app
RUN apk add --update bash openssl

RUN mkdir /app
WORKDIR /app

COPY --from=build /app/_build/prod/rel/hello_world ./
RUN chown -R nobody: /app
USER nobody

ENV HOME=/app

ENTRYPOINT ["./bin/hello_world", "start"]

J'essaie d'exécuter le conteneur avec cette commande:

docker run -it -e DATABASE_URL='ecto://postgres:123456@localhost/hello_world_dev' -e SECRET_KEY_BASE='blargblargblarg' hello_world:latest

Mais je reçois cette erreur:

05:20:34.841 [error] GenServer #PID<0.1380.0> terminating
** (RuntimeError) connect raised KeyError exception: key :database not found. The exception details are hidden, as they may contain sensitive data such as database credentials. You may set :show_sensitive_data_on_connection_error to true when starting your connection if you wish to see all of the details
    (elixir) lib/keyword.ex:393: Keyword.fetch!/2
    (postgrex) lib/postgrex/protocol.ex:92: Postgrex.Protocol.connect/1
    (db_connection) lib/db_connection/connection.ex:69: DBConnection.Connection.connect/2
    (connection) lib/connection.ex:622: Connection.enter_connect/5
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: nil

..... repeat a lot

05:48:41.296 [info] Application hello_world exited: shutdown

Tout cela est très nouveau pour moi. J'ai déployé facilement sur Heroku, mais j'aimerais que le chemin heureux utilisant Docker soit déployé sur AWS, GCP, etc.

Mise à jour 1: voici le config/prod.secret.exs fichier. Je ne l'ai pas modifié. Veuillez noter qu'il charge des variables d'environnement:

# In this file, we load production configuration and secrets
# from environment variables. You can also hardcode secrets,
# although such is generally not recommended and you have to
# remember to add this file to your .gitignore.
use Mix.Config

database_url =
  System.get_env("DATABASE_URL") ||
    raise """
    environment variable DATABASE_URL is missing.
    For example: ecto://USER:PASS@Host/DATABASE
    """

config :hello_world, HelloWorld.Repo,
  # ssl: true,
  url: database_url,
  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

secret_key_base =
  System.get_env("SECRET_KEY_BASE") ||
    raise """
    environment variable SECRET_KEY_BASE is missing.
    You can generate one by calling: mix phx.gen.secret
    """

config :hello_world, HelloWorldWeb.Endpoint,
  http: [:inet6, port: String.to_integer(System.get_env("PORT") || "4000")],
  secret_key_base: secret_key_base

# ## Using releases (Elixir v1.9+)
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start each relevant endpoint:
#
#     config :hello_world, HelloWorldWeb.Endpoint, server: true
#
# Then you can assemble a release by calling `mix release`.
# See `mix help release` for more information.

Mise à jour 2:

J'ai enlevé import_config "config/prod.secret.exs" de config/prod.exs J'ai renommé config/prod.secret.exs à config/releases.exs et dans le fichier j'ai changé use Mix.Config à import Config et sans commentaires config :hello_world, HelloWorldWeb.Endpoint, server: true

Maintenant, quand j'essaie d'exécuter le conteneur comme ça avec le mappage de port et --network = "Host"

docker run -it -e DATABASE_URL='ecto://postgres:123456@localhost/hello_world_dev' -e SECRET_KEY_BASE='blargblargblarg' -p 5432:5432 --network="Host" hello_world:latest

Je reçois

23:21:48.635 [error] Postgrex.Protocol (#PID<0.2615.0>) failed to connect: ** (DBConnection.ConnectionError) tcp connect (localhost:5432): connection refused - :econnrefused
23:21:48.635 [error] Postgrex.Protocol (#PID<0.2619.0>) failed to connect: ** (DBConnection.ConnectionError) tcp connect (localhost:5432): connection refused - :econnrefused
23:21:48.639 [info] Running HelloWorldWeb.Endpoint with cowboy 2.6.3 at :::4000 (http)
23:21:48.640 [info] Access HelloWorldWeb.Endpoint at http://example.com
23:21:49.938 [error] Postgrex.Protocol (#PID<0.2623.0>) failed to connect: ** (DBConnection.ConnectionError) tcp connect (localhost:5432): connection refused - :econnrefused

Mise à jour 3: j'ai essayé de changer le DATABASE_URL mais aucun n'a fonctionné.

# adding port
DATABASE_URL='ecto://postgres:123456@localhost:5432/hello_world_dev'

# changing hostname to 127.0.0.1
DATABASE_URL='ecto://postgres:[email protected]/hello_world_dev'

# changing hostname to 127.0.0.1 and adding port
DATABASE_URL='ecto://postgres:[email protected]:5432/hello_world_dev'

# changing hostname to 0.0.0.0
DATABASE_URL='ecto://postgres:[email protected]/hello_world_dev'

# changing hostname to 0.0.0.0 and adding port
DATABASE_URL='ecto://postgres:[email protected]:5432/hello_world_dev'

# changing ecto to postgresql
DATABASE_URL='postgresql://postgres:123456@localhost/hello_world_dev'

# changing ecto to postgresql and localhost to 127.0.0.1
DATABASE_URL='postgresql://postgres:[email protected]/hello_world_dev'

# changing ecto to postgresql and localhost to 127.0.0.1 with port
DATABASE_URL='postgresql://postgres:[email protected]:5432/hello_world_dev'

# changing ecto to postgresql and localhost to 0.0.0.0
DATABASE_URL='postgresql://postgres:[email protected]/hello_world_dev'

# changing ecto to postgresql and localhost to 0.0.0.0 with port
DATABASE_URL='postgresql://postgres:[email protected]:5432/hello_world_dev'

¯\_(ツ)_/¯
6
Loading...

Définir l'URL de la base de données à partir de la variable ENV

config :hello_world, HelloWorld.Repo,
  url: "${DATABASE_URL}"

Utilisez ensuite l'url de la base de données avec pool_size lorsque vous le passez au conteneur

DATABASE_URL=ecto://postgres@db/hello_world?pool_size=10

Ajouter ENV REPLACE_OS_VARS=true à votre Dockerfile (ne peut fonctionner qu'avec la distillerie, sinon remplacez "${DATABASE_URL}" avec la forme correcte pour obtenir la variable ENV au moment de l'exécution, pas lors de la compilation).

Voici un exemple de création de build simple avec Distillery:

FROM elixir:1.9.0-Alpine

RUN mix local.hex --force && \
    mix local.rebar --force && \
    mix archive.install --force hex phx_new 1.4.8

RUN apk add --update nodejs nodejs-npm

ENV MIX_ENV=prod

WORKDIR /srv/app

COPY ./platform/ /srv/app/

# install dependencies (production only)
RUN mix local.rebar --force
RUN mix deps.get --only prod
RUN mix compile

# RUN npm install --global webpack
RUN cd assets && npm install && ./node_modules/webpack/bin/webpack.js --mode production
RUN mix phx.digest

RUN mix distillery.release

# Alpine version should be the same as build
FROM Alpine:3.9
RUN apk add --update bash
ENV REPLACE_OS_VARS=true
WORKDIR /srv/app
COPY --from=0  /srv/app/_build/prod/ .
CMD rel/platform/bin/platform migrate && rel/platform/bin/platform foreground

N'oubliez pas non plus que postgres s'exécute localement (pas sur le même réseau que le conteneur), vous devez le prendre en compte et utiliser docker.for.mac.localhost comme nom d'hôte pour postgres ou --net=Host pour docker run et localhost comme hôte pour postgres.

1
achempion

Il semble que le problème apparaisse car vous n'avez pas correctement défini la configuration.

exception: clé: base de données introuvable

Pour utiliser la base de données que vous avez injectée à l'aide de variables d'environnement, vous devez l'obtenir explicitement. Selon la façon dont vous avez organisé votre config, vous devriez avoir une config pour votre Repo. Par défaut, la configuration du dépôt est:

config :yourserver_api, YourServer.Repo,
  username: "postgres",
  password: "postgres",
  database: "yourserver_api_prod",
  hostname: "localhost",
  pool_size: 10

Si vous savez avec certitude que, par exemple, la base de données sera injectée, vous devez la changer en:

database: System.get_env("DATABASE_URL") 
0
Daniel