Phoenix 1.6.0.rc-0 esbuild assets.deploy fails in production

I have a Phoenix 1.6 app deployed on fly.io, I followed the official guides by fly and the new 1.6 docs to deploy to fly.io. I have also integrated tailwindcss and custom fonts on the pipeline. The deploy fails at assets.deploy because it then checks for runtime secrets that are not present in the builder that fly.io uses.

Here is my Dockerfile

###
### Fist Stage - Building the Release
###
FROM hexpm/elixir:1.12.1-erlang-24.0.1-alpine-3.13.3 AS build

# install build dependencies
RUN apk add --no-cache build-base npm

# prepare build dir
WORKDIR /app

# extend hex timeout
ENV HEX_HTTP_TIMEOUT=20

# install hex + rebar
RUN mix local.hex --force && \
    mix local.rebar --force

# set build ENV as prod
ENV MIX_ENV=prod
ENV SECRET_KEY_BASE=nokey

# Copy over the mix.exs and mix.lock files to load the dependencies. If those
# files don't change, then we don't keep re-fetching and rebuilding the deps.
COPY mix.exs mix.lock ./
COPY config config

RUN mix deps.get --only prod && \
    mix deps.compile

# install npm dependencies
COPY assets/package.json assets/package-lock.json ./assets/
RUN npm --prefix ./assets ci --progress=false --no-audit --loglevel=error

COPY priv priv
COPY assets assets

# NOTE: If using TailwindCSS, it uses a special "purge" step and that requires
# the code in `lib` to see what is being used. Uncomment that here before
# running the npm deploy script if that's the case.
COPY lib lib

# build assets
# RUN npm run --prefix ./assets deploy
# RUN mix phx.digest
RUN mix assets.deploy

# copy source here if not using TailwindCSS
COPY lib lib

# compile and build release
COPY rel rel
RUN mix do compile, release

###
### Second Stage - Setup the Runtime Environment
###

# prepare release docker image
FROM alpine:3.13.3 AS app
RUN apk add --no-cache libstdc++ openssl ncurses-libs

WORKDIR /app

RUN chown nobody:nobody /app

USER nobody:nobody

COPY --from=build --chown=nobody:nobody /app/_build/prod/rel/indie_paper ./

ENV HOME=/app
ENV MIX_ENV=prod
ENV SECRET_KEY_BASE=nokey
ENV PORT=4000

CMD ["bin/indie_paper", "start"]

Here is my runtime config file

if config_env() == :prod do
  database_url =
    System.get_env("DATABASE_URL") ||
      raise "
      environment variable DATABASE_URL is missing.
      For example: ecto://USER:PASS@HOST/DATABASE
      "

  config :indie_paper, IndiePaper.Repo,
    # ssl: true,
    socket_options: [:inet6],
    url: database_url,
    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

  secret_key_base =
    System.get_env("SECRET_KEY_BASE") ||
      raise "
      environment variable SECRET_KEY_BASE is missing.
      You can generate one by calling: mix phx.gen.secret
      "

  app_name =
    System.get_env("FLY_APP_NAME") ||
      raise "FLY_APP_NAME not available"

  config :indie_paper, IndiePaperWeb.Endpoint,
    server: true,
    url: [host: "#{app_name}.fly.dev", port: 80],
    http: [
      ip: {0, 0, 0, 0, 0, 0, 0, 0},
      port: String.to_integer(System.get_env("PORT") || "4000")
    ],
    secret_key_base: secret_key_base

Here is my updated parts of the mix.exs file

  defp aliases do
    [
      setup: ["deps.get", "ecto.setup", "cmd --cd assets npm install"],
      "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
      "ecto.reset": ["ecto.drop", "ecto.setup"],
      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
      "assets.deploy": [
        "cmd --cd assets npm run deploy",
        "esbuild default --minify",
        "phx.digest"
      ]
    ]
  end

The build command fails at esbuild during the build phase with the following error. This might be because fly.io uses a separate build machine that builds the image and then pushes it onto the machine with the secrets. How do I fix this ?

Step 16/30 : RUN mix assets.deploy
 ---> Running in c2f024ff203d

> @ deploy /app/assets
> NODE_ENV=production postcss css/app.css -o ../priv/static/assets/app.css


warn - You have enabled the JIT engine which is currently in preview.
warn - Preview features are not covered by semver, may introduce breaking changes, and can change at any time.
Compiling 34 files (.ex)
Generated indie_paper app
** (RuntimeError) environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE

    (stdlib 3.15) erl_eval.erl:685: :erl_eval.do_apply/6
    (stdlib 3.15) erl_eval.erl:446: :erl_eval.expr/5
    (stdlib 3.15) erl_eval.erl:123: :erl_eval.exprs/5
    (elixir 1.12.1) lib/code.ex:656: Code.eval_string_with_error_handling/3
    (elixir 1.12.1) lib/config.ex:258: Config.__eval__!/3
    (elixir 1.12.1) lib/config/reader.ex:86: Config.Reader.read!/2

Error error building: error rendering build status stream: The command '/bin/sh -c mix assets.deploy' returned a non-zero code: 1

If I disable the esbuild step the deployment succeeds.

Hi

Last weekend I deployed a Phoenix 1.6 app to fly.io and ran into the same esbuild error that you encountered :slight_smile:

I fixed it by having the following line in my Dockerfile

RUN DATABASE_URL=ignore FLY_APP_NAME=ignore mix assets.deploy

Does that work for you?

1 Like

Digging deeper I came across this issue https://github.com/phoenixframework/esbuild/issues/18 which adds a --no-runtime-config option to esbuild.

This makes my config

      "assets.deploy": [
        "cmd --cd assets npm run deploy",
        "esbuild --no-runtime-config default --minify",

Since the version is not released yet I’ve had to update my esbuild dependency to {:esbuild, git: "https://github.com/phoenixframework/esbuild", runtime: Mix.env() == :dev},

Curently deploying, will come back with results.

EDIT: Deployed and the error went away. Also submitted a PR to generators https://github.com/phoenixframework/phoenix/pull/4458

7 Likes

The latest version of esbuild ships with the flag by default. So these changes are no longer neccesary.

2 Likes

Are you using webpack? You cannot run npm run deploy without webpack.

Thanks for sharing this Dockerfile. I was having issues where css styles would not appear on Prod. Along with a couple of other changes and this Dockerfile I got it to work.

When I updated to Phoenix 1.6 + tailwind my app.css broke in prod but not dev. So it took quite a long time to figure it out.

  • I forgot to add assets to the static paths in endpoint.ex

  • many app.css references were broken

  • this was the hardest part: my Dockerfile was copying files to the image in the wrong order. This post helped a lot here I updated my dockerfile to your sequence

3 Likes