Multi-stage Docker image for phoenix

Maybe I should give it another try.

Btw, did you ever encounter that yarn install needs phoenix and phoenix_html deps that come from mix? I don’t see how you dealt with it.

I modified my package.json to grab phoenix and phoenix_html straight from npm as a regular package.

You can find them at:

And you can use them like normal in your JS, such as: import "phoenix";. Assuming you’re using ES6 style JS.

1 Like

Doh. I had no idea they are available in npm.

Thanks a lot!

No problem. It took a while to wire all of that together, but now that it works, it’s really nice. It’s a setup that will work in both dev and prod using the same docker-compose.yml file because in prod I just wouldn’t use the docker-compose.override.yml file, because in prod nginx would serve the compiled assets instead of running the webpack dev server.

That behavior is dictated by the build args that you can set with ENV variables.

I really wish mix deps.get and mix deps.compile work only with mix.lock (see Mix install in umbrella projects without mix.exs?)

Then we’ll get huge boost on build time for unrelated changes. For example, if you change a mix file to add an option… boom! docker cannot use cache.

Also it’s very cumbersome as you need to add all mix files from all apps in umbrella project. You have 10 umlbreea apps? Then you need to have 10 COPY command to copy all mix files into the right place.

If anyone is interested, here is my latest Docker setup. I hardcoded everything for development. For production I will probably do it differently and build a release. But for development it works just great.


FROM node:12.4-alpine as webpack

RUN npm install -g yarn

WORKDIR /app/assets

COPY assets/package.json assets/*yarn* ./

RUN apk --update --upgrade add ca-certificates build-base bash \
    && update-ca-certificates --fresh \
    && rm -rf /var/cache/apk/*  \
    && yarn install \
    && apk del build-base

COPY assets .

ENV NODE_ENV="development"

CMD ["tail", "-f", "/dev/null"]


FROM elixir:1.8-otp-22-alpine as app


RUN apk --update --upgrade add ca-certificates inotify-tools postgresql-client bash \
    && update-ca-certificates --fresh \
    && rm -rf /var/cache/apk/*

COPY ./docker/dev/ /
COPY mix.* ./

ENV MIX_ENV="dev" \
    MIX_HOME=/opt/mix \

RUN mix local.hex --force \
    && mix local.rebar --force \
    && mix deps.get \
    && mix deps.compile

CMD ["tail", "-f", "/dev/null"]


version: '3.7'

    image: postgres:11-alpine
    container_name: project_postgres
    restart: always
      - 15432:5432
      - ./docker/data/postgres:/var/lib/postgresql/data

      context: .
      dockerfile: ./docker/dev/Dockerfile
      target: "webpack"
    container_name: project_webpack
    restart: unless-stopped
    command: yarn run watch
      - .:/app
      - static:/app/priv/static

      context: .
      dockerfile: ./docker/dev/Dockerfile
      target: "app"
    container_name: project_app
      - 4000:4000
    command: tail -f /dev/null
      - .:/app
      - static:/app/priv/static
      - deps:/app/deps
      - build:/app/_build
      - postgres
      - webpack

  static: {}
  deps: {}
  build: {}



set -e


until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$host" -U "postgres" -c '\q'; do
  >&2 echo "Postgres is unavailable - sleeping"
  sleep 1

>&2 echo "Postgres is up - executing command"
exec $cmd

And Makefile

.PHONY: app server console sh clean

	docker-compose up --detach --build app

setup: app
	docker-compose exec app / postgres mix ecto.setup

server: setup
	docker-compose exec app mix phx.server

console: setup
	docker-compose exec app iex -S mix

sh: setup
	docker-compose exec app bash

	docker-compose down

Now I can just execute make console or make server and it will start all services and even wait for postgres to be healthy and execute migrations before starting the server on iex.

Thanks @nickjanetakis for the helpful tips.


FWIW, I’ve captured a quick tutorial on minimal (Alpine) Multi-Stage Docker image builds with Elixir 1.9 and Phoenix in this Gist:

1 Like

Hi @nickjanetakis,

I was wondering if you have these dockerfiles and compose files open sourced? I’ve read many blog posts with slightly different approaches and just not sure how to tie it all together for a basic generated phoenix umbrella app (with ecto). I’m trying to get a development environment (editing using vscode remote container plugin) and a mix release driven multi-stage build delivering the .tar.gz release ready for deployment in a container in the same project.

I don’t have one for Phoenix but here’s the Docker set up for my Flask course

The only thing that would change for Phoenix is you would be doing Elixir’y things in the “app”'s Dockerfile stage instead of Python’y things. The structure of the Docker Compose file, Webpack, env variables and everything is the same between both stacks.

You’ll just want to make sure you have these set in your mix.iex for the def project:

      build_path: "/elixir/_build",
      deps_path: "/elixir/deps",

And in config/dev.exs you’ll want to set watchers: [] for your HelloWeb.Endpoint since a separate Webpack container will be spawned that handles watching for you.

1 Like