Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I run Elixir Phoenix Docker in production mode using example from Phoenix Guides?

The example Dockerfile given on the Elixir Phoenix Guides appears to be outdated and broken. The example is found here: https://hexdocs.pm/phoenix/releases.html#containers

I created a vanilla application like like so: mix phx.new hello_world

The broken Dockerfile:

# 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

I am trying to run the Dockerfile from a vanilla Phoenix app install and have run into many issues including:

# 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

I am no expert and so far the Dockerfile now look like this:

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"]

I try to run the container with this command:

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

But I get this error:

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

This is all very new to me. I have deployed to Heroku easily but would like the happy path using Docker to deploy to AWS, GCP, etc...

Update 1: Here is the config/prod.secret.exs file. I have not modified it. Please note it is loading environment variables:

# 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.

Update 2:

I removed import_config "config/prod.secret.exs" from config/prod.exs I renamed config/prod.secret.exs to config/releases.exs and in the file I changed use Mix.Config to import Config and uncommented config :hello_world, HelloWorldWeb.Endpoint, server: true

Now when I try to run the container like so with port mapping and --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

I get

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

Update 3: I have tried changing the DATABASE_URL but none worked.

# 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'

¯\_(ツ)_/¯
like image 540
Loading... Avatar asked Sep 30 '19 07:09

Loading...


2 Answers

There is a good sample repo is configured for production-ready build and deploy cycle. It contains an ansible setup that will maintain docker image, build phoenix app in docker image, and do automated versioned releases on your production server.

And I recommend you to read the blog post guide

like image 172
Sergey Chechaev Avatar answered Sep 28 '22 19:09

Sergey Chechaev


Define database url from ENV variable

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

Then use the database url with pool_size as you pass it to container

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

Add ENV REPLACE_OS_VARS=true to your Dockerfile (may only work with Distillery, otherwise substitute "${DATABASE_URL}" with correct form to get ENV variable at runtime, not on compiling stage).

Here is example how to create simple build with 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

Also, keep in mind since you have postgres running locally (not on the same network as container), you need to take it into accout and use docker.for.mac.localhost as the host name for postgres or --net=host for docker run and localhost as the host for postgres.

like image 21
achempion Avatar answered Sep 28 '22 18:09

achempion