Phoenix Development Environment in Docker
The goal of this post is to setup a development environment for a Phoenix web app on macOS in docker. We will shoot to minimize dependencies on the host and also the amount of time manipulating the containers with interactive shells. The end state will be a directory called workdir
with the following structure:
.
├── Dockerfile # Working dockerfile for running development commands
├── app # source code for the phoenix app
├── database # volume for the postgres database
├── docker-compose-dev.yml # development docker-compose (not for production)
├── docker-compose.yml # production docker-compose
└── docker-sync.yml # docker-sync for developing the source of the application
Assumed Dependencies
- macOS (Catalina 10.15.5 in this example)
- Docker Desktop Community installed (version 2.3.0.3, engine 19.03.8, compose 1.25.5 )
- git (2.26.2)
- ruby environmentRuby comes installed in macOS, but you likely will want to use a ruby version manager and to avoid mussing with system gems. A good setup can be found here.
We will start by creating a Dockerfile with node and phoenix on top of the default elixir container. Notice that we are using 1.10.3 of elixir and phoenix 1.5.3. These version numbers can obviously be updated to the latest relevant version.
FROM elixir:1.10.3-alpine
MAINTAINER Michael Cordell <mike@mikecordell.com>
ENV DEBIAN_FRONTEND=noninteractive
ENV MIX_ENV=dev
RUN mix local.hex --force \
&& mix archive.install --force hex phx_new 1.5.3 \
&& apk add --update nodejs \
&& apk add --update npm \
&& apk add --update inotify-tools \
&& mix local.rebar --force
WORKDIR /workdir
We can then use this Dockerfile to create/scaffold the phoenix appHere APP_NAME is a placeholder for whatever you want to all your app. You do not want to use a generic name like “App” here because it is used in the scaffolding of the Phoenix app.
docker run --volume `pwd`:/workdir -it $(docker build -q .) mix phx.new APP_NAME
This produces the following reminders about how to setup the app, we will get to these in a second.
Phoenix uses an optional assets build tool called webpack
that requires node.js and npm. Installation instructions for
node.js, which includes npm, can be found at http://nodejs.org.
The command listed next expect that you have npm available.
If you don't want webpack, you can re-run this generator
with the --no-webpack option.
We are almost there! The following steps are missing:
$ cd APP_NAME
$ cd assets && npm install && node node_modules/webpack/bin/webpack.js --mode development
Then configure your database in config/dev.exs and run:
$ mix ecto.create
Start your Phoenix app with:
$ mix phx.server
You can also run your app inside IEx (Interactive Elixir) as:
$ iex -S mix phx.server
First we move the scaffolded folder into it’s position as “app”:
mv APP_NAME app
Now we can install docker-sync which is used to speed up the syncing of the source code of the application into the development container.
gem install docker-sync
With docker-sync installed, we can configure it. Note the name of app-name-sync
, you can change it to match your custom app name. This name just needs to match in the following docker-compose-dev.yml.
version: "2"
options:
# optional, maximum number of attempts for unison waiting for the success exit
# status. The default is 5 attempts (1-second sleep for each attempt). Only
# used in unison.
max_attempt: 200
syncs:
app-name-sync:
# os aware sync strategy, defaults to native_osx under MacOS (except with docker-machine which use unison), and native docker volume under linux
# remove this option to use the default strategy per os or set a specific one
sync_strategy: 'native_osx'
# which folder to watch / sync from - you can use tilde, it will get expanded.
# the contents of this directory will be synchronized to the Docker volume with the name of this sync entry ('shortexample-sync' here)
src: './app/'
host_disk_mount_mode: 'cached' # see https://docs.docker.com/docker-for-mac/osxfs-caching/#cached
# other unison options can also be specified here, which will be used when run under osx,
# and ignored when run under linux
Now we can setup the docker-compose file for development. Having a separate docker-compose file allows for the custom configuration of the docker-sync volume while keeping it separate from the production configuration. In this configuration, we are pairing the phoenix application with a Postgres container and volumeStored at ./database
within our working dir..
version: "2"
services:
web:
build:
context: ./app/
dockerfile: ../Dockerfile
working_dir: /app
ports:
- "4000:4000"
command: mix phx.server
environment:
- MIX_ENV=dev
- PORT=4000
- PG_HOST=db
- PG_USERNAME=postgres
- PG_PASSWORD=postgres
volumes:
- real-estate-sync:/app:nocopy # nocopy is important
links:
- db
db:
image: postgres:12.3-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_HOST=db
volumes:
- ./database:/var/lib/postgresql/data
volumes:
real-estate-sync:
external: true
Before we can start using the docker-compose setup we need to configure the development configuration in the application source to point at the docker configuration. This is done by modifying the app/config/dev.exs
to use the docker username, password, and host environment variables:
diff --git a/config/dev.exs b/config/dev.exs
index 6d184c3..332d83a 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -2,10 +2,10 @@ use Mix.Config
# Configure your database
config :app_name, AppName.Repo,
- username: "postgres",
- password: "postgres",
+ username: System.get_env("PG_USERNAME"),
+ password: System.get_env("PG_PASSWORD"),
+ hostname: System.get_env("PG_HOST"),
database: "app_name_dev",
- hostname: "localhost",
show_sensitive_data_on_connection_error: true,
pool_size: 10
Now we can run database mix commands and create the database:
docker-compose -f docker-compose-dev.yml run web mix ecto.create
Finally, we need to setup up the front end dependencies and webpack. We cheat a bit here an use the interactive shell:
docker-compose -f docker-compose-dev.yml run web /bin/sh
In the web container:
cd /app/assets
npm install && node node_modules/webpack/bin/webpack.js --mode development
exit
To clean up a bit, lets commit this initial source:
cd app
git init .
git commit -m "Initial commit"
cd ..
Lets test out our setup by bringing up the docker-compose-dev file:
docker-compose -f docker-compose-dev.yml up
Navigate to http://localhost:4000
Open the file app/lib/app_name_web/templates/page/index.html
and modify some visible text. For example, changing the paragraph tag from: Peace-of-mind from prototype to production
to Running in docker
.
Save the file, your browser should live reload reflecting your change. You now have a working development in environment for phoenix!