3

I am very new to Elixir. I have built an app which runs locally, which works fine. But now i need to build a container for it with Docker.

However, every attempt to do a release seems to try and connection to RabbitMQ (which is running locally, as a Docker Container).

I don't want, cant have it try and connect to Rabbit each time this container is built, as it will need to be built by a CI / CD pipeline and will never have access to any Rabbit. I have set it up with an ENV, but this needs to be set within my YAML when deploying to my k8s cluster.

So this is the Dockerfile:

# Container Base Image
FROM elixir:1.13.1-alpine as release_build

# Set Working Folder
WORKDIR app

#ENV RABBIT_CONNECTION=""

# Copy Source Code Into Container
COPY ./src/app ./

# Install Hex Package Manager
RUN mix local.hex --force

# Install Rebar
RUN mix local.rebar --force

# Get All Deps
RUN mix deps.get

RUN mix deps.compile

# Compile Application
RUN mix compile

So has you can see i have a ENV set with the Rabbit Connection URL (commented out currently). In this state i get,

The following arguments were given to AMQP.Connection.open/1:

If i set it, but only to an empty string, i get,

no match of right hand side value: {:error, {{:unable_to_parse_uri, :no_scheme}, []}}

I also tried with the valid URL of my locally running container, but has it was using localhost and on a different Docker network, that just returned an econnrefused error.

This is how I am connecting to Rabbit in my app,

rabbit_url = Application.fetch_env!(:rabbit, :url)

# Open Connection To Rabbit
{:ok, connection} = AMQP.Connection.open(rabbit_url)
{:ok, channel} = AMQP.Channel.open(connection)

This is the rabbit config file section,

config :rabbit, url: System.get_env("RABBIT_CONNECTION")

I made a little local Bash script to boot this,

RABBIT_CONNECTION='amqp://admin:password@localhost:5672' iex -S mix

This script works fine with the application to boot and connection to my locally running container for RabbitMQ

So I know there must be a way of getting the code to do a release without an connections, either to Rabbit or a Database or something similar like that.

Any help is most welcome

Thanks,

C0ol_Cod3r
  • 909
  • 2
  • 15
  • 35

2 Answers2

3

I have tried to create a project such as yours.

mix new broker_client

In the mix.exs application function is configured to run the start function of my module.

def application do
  [
    extra_applications: [:logger],
    mod: {BrokerClient, []}
  ]
end

Additionally, in config/runtime.exs, I am configuring amqp with the connection and one or more channels as documented here.

import Config

config :amqp,
  connections: [
    myconn: [url: System.get_env("BROKER_URL")]
  ],
  channels: [
    mychan: [connection: :myconn]
  ]

In lib/broker_client.ex I have the start function implemented which creates a simple Task as showing in this answer.

defmodule BrokerClient do
  def sample() do
    {:ok, chan} = AMQP.Application.get_channel(:mychan)
    :ok = AMQP.Basic.publish(chan, "", "", "Hello")
    Process.sleep(1000 * 10)
  end

  def start(_type, _args) do
    IO.puts("starting...")
    Task.start(fn -> sample() end)
  end
end

I can build this fine without having rabbitmq running locally or setting the variable broker.

FROM elixir as builder
WORKDIR /app
RUN mix local.hex --force && mix local.rebar --force
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
COPY  ./ .
RUN MIX_ENV=prod mix release

FROM debian:stable-slim
ENV LANG="C.UTF-8" LC_AL="C.UTF-8" PATH="/app/bin:$PATH"
COPY --from=builder /app/_build/prod/rel/broker_client /app
CMD [ "broker_client", "start"]

Now I can run this with docker-compose as example.

version: '3.9'

services:
  client:
    build: ./
    environment:
      BROKER_URL: 'amqp://guest:guest@rabbitmq'
    # sleep 10 seconds to give the broker time to start
    command: [ "sh", "-c", "sleep 10 && broker_client start" ]
    depends_on:
      - rabbitmq
  rabbitmq:
    image: rabbitmq

Its probably also useful to look at the offical docs for Application.

The Fool
  • 16,715
  • 5
  • 52
  • 86
  • So this project was setup with (please forgive me if I am wrong, still learning) a supervisor? I thought it was best not to, unless needed for something? – C0ol_Cod3r Jan 14 '22 at 21:59
  • also this helps a lot, the code for connection to Rabbit was outside of my module, so it would just connection to rabbit when lunched, i have now moved into into its own function - is it common please to name these functions as start? – C0ol_Cod3r Jan 14 '22 at 22:01
  • I think its not really using a supervisor. Although using one may be better. I dont really know elixir tbh. Maybe it would make to create the project with `mix new --sup.` If you want to run the code like this you have to implement the *start* method of the Application, I have most of the things here from the documentation, including the start method https://hexdocs.pm/elixir/1.13.2/Application.html#module-the-application-callback-module – The Fool Jan 14 '22 at 22:55
  • This does work, at lest for me for local - i see if its stable or needs a supervisor (which i am still trying to get my head around). The app is very simple, it just makes html files from data sent into Rabbit. – C0ol_Cod3r Jan 14 '22 at 23:06
  • I have used a Task now and also improved the amqp config. – The Fool Jan 14 '22 at 23:33
1

You need to use the runtime configuration, otherwise the configuration must be passed at compile-time, to do this all you have to do is add runtime.exs and to get the environment variable there:

config :rabbit, url: System.get_env("RABBIT_CONNECTION")
Daniel
  • 2,320
  • 1
  • 14
  • 27