Setup Phoenix on Docker with MongoDB

mhz
mhz
Jun 11 · 6 min read
Image for post
Image for post

First and foremost, this was a learning experiment — probably not something I would do on a production-level app given what I learned.

I was trying to create an API endpoint for serving Dungeons & Dragons (D&D) items data. My data would come from an existing JSON file.

I wanted to use the Phoenix framework to learn functional programming. My data would be read-only, but it had an irregular shape. Because of that I thought it would be best to use a NoSQL data store so to circumvent the complications of designing the schema for a relational database over a huge set of data with no clear relationship patterns. I picked MongoDB because it was popular, and I wanted to try it out. I picked Docker because I didn’t want to install MongoDB in my system.

Finally I wasn’t able to find any up-to-date sources online on how to get this setup running, so I decided to write one myself as part of my learning exercise.

Words of Warning

Elixir runs on the Beam VM, which allows parallel execution based on how many cores exist in the machine. A Docker container may have settings that limit the maximum amount of cores available to containers, in which case may or may not be desirable for some applications. For mine, it doesn’t really matter.

All the versions

Elixir
v1.10.3
Dependencies
phoenix ~> 1.4.3
mongodb ~> 0.5.1
poolboy ~> 1.5.2
MongoDB
v4.2.6
Docker version 19.03.8, build afacb8bdocker-compose version 1.25.5, build 8a1c60f6docker-compose.yml
version: "3.8"

Step 1: Remove Ecto & Postgres defaults

A benefit of keeping Ecto even without using the MongoDB adapter is to have access to Ecto.Schema for typecasting and data validation. I didn’t want to enforce a “schema” for my application because it would add unnecessary obstacles. Also, my data will almost strictly be read-only — seeded only once— which would eliminate the need for validation in run-time.

Remove from "mix.exs"

{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"},

Remove from "config/config.exs"

config :my_app,
ecto_repos: [MyApp.Repo]

Remove from “config/dev.exs”

config :my_app, MyApp.Repo,
username: "postgres",
password: "postgres",
database: "my_app_dev",
hostname: "localhost",
pool_size: 10

Remove from “config/test.exs”

config :my_app, MyApp.Repo,
username: "postgres",
password: "postgres",
database: "my_app_test",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox

Remove the file “lib/my_app/repo.ex”

Remove mentions of “Ecto.Adapters.SQL.Sandbox*”

  • test/support/channel_case.ex
  • test/support/conn_case.ex
  • test/support/data_case.ex
  • test/test_helper.exs

You setup methods will become very slim afterwards.

Remove mentions of “MyApp.Repo”

Step 2: Setup connection to MongoDB

Adding connection worker to the Supervisor

I chose to pass in the broken down arguments, specifically hostname because we will need it when firing it up with docker-console. For now, I have called it mongo as we will name the docker-compose service later. To get it working at this point, you can simply set it to localhost.

children = [
...
worker(
Mongo,
[[
database: "my_app_db",
hostname: "mongo",
username: "root",
password: "rootpassword"
]]
),
...
]

Initialization script for MongoDB

Run this JS script on your MongoDB before starting your Phoenix server. Lets call this init-mongo.js for now. We will circle back to this later.

db.createCollection("my_collection")db.createUser(
{
user: "root",
pwd: "rootpassword",
roles: [
{
role: "dbOwner",
db: "my_app_db"
}
]
}
)

For simplicity sake, we will be giving the user root dbOwner permissions.

At this point, you should be able to run start your MongoDB and your Phoenix server with mix phx.server and have it connect to the MongoDB database on startup.

Step 3: Setup your application in Docker

Create a Dockerfile

FROM elixir:1.10.3RUN mkdir app/
COPY . /app
WORKDIR app/RUN mix local.hex --force
RUN mix local.rebar --force
RUN mix deps.get
RUN mix do compile

Create a `docker-compose.yml`

  • MongoDB: Set environment variables to initialize DB and user access
  • MongoDB: Include and run init-mongo.js so it gets setup at DB start-up
  • App: Builds an image after the Dockerfile
  • App: Expose port 4000 for access at localhost:4000
  • App: Mount volume ./:app so file changes in the source code of the app reflect immediately in the container (debugging, development)
version: "3.8"services:mongo:
image: mongo:latest
container_name: my_app_mongo
environment:
MONGO_INITDB_DATABASE: my_app_db
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: rootpassword
ports:
- 27017:27017
volumes:
- mongodb_data_container:/data/db
- ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
- ./_build:/app/_build
app:
build:
context: .
container_name: my_app_name
ports:
- "4000:4000"
volumes:
- ./:/app
command: mix phx.server

Step 4: Making sure everything works

From the “app” service

From the “mongo” service

When you start your app, it will connect to mongoDB, and you should see in the logs the following message.

NETWORK  [listener] connection accepted from INTERNAL_IP:60901 #1 (1 connection now open)
NETWORK [listener] connection accepted from INTERNAL_IP:55015 #2 (2 connections now open)

That’s it!

Step 5: Writing and reading from MongoDB

Mongo.start_link(
name: :mongo,
database: "top_role_items_db",
hostname: "mongo",
username: "root",
password: "rootpassword"
)
data = Utils.DataConverter.fetch_items
{:ok, _} = Mongo.insert_many(:mongo, "items", data)
Mongo.count(:mongo, "items", %{})
|> IO.inspect(label: "Number of inserted items")

The full documentation provides examples to other ways you can use the MongoDB driver.

Conclusion

  • How Phoenix’s Supervisor works
  • How to setup, add users, and collections to MongoDB
  • Docker volumes and mounts

This is probably not a good stack combination to use for my application, just based on the lack of fuzzy text search capabilities of MongoDB.

References

The Startup

Medium's largest active publication, followed by +721K people. Follow to join our community.

mhz

Written by

mhz

Software Engineer. Using this platform to share knowledge on software, and reflections in life.

The Startup

Medium's largest active publication, followed by +721K people. Follow to join our community.

mhz

Written by

mhz

Software Engineer. Using this platform to share knowledge on software, and reflections in life.

The Startup

Medium's largest active publication, followed by +721K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store