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 Ruby 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.
- macOS (Catalina 10.15.5 in this example)
- Docker Desktop Community installed (version 188.8.131.52, engine 19.03.8, compose 1.25.5 )
- git (2.26.2)
- ruby environment
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 <email@example.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 app Here 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
./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: - app-name-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: app-name-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!