Tailwind CSS classes are not loaded in production release deployments (docker/fly.io)

I am trying to migrate our app (that uses Tailwindcss, and React on the frontend) from Phoenix v1.5 to v1.6.11

Everything works perfectly on local, but the CSS classes are not being loaded in production deployments.

dockerfile
ARG ELIXIR_VERSION=1.13.4
ARG OTP_VERSION=25.0.3
ARG ALPINE_VERSION=3.14.6

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-alpine-${ALPINE_VERSION}"
ARG RUNNER_IMAGE="alpine:${ALPINE_VERSION}"

#####################################################
# 1. Build elixir backend and compile assets
#####################################################
FROM ${BUILDER_IMAGE} as builder

# install build dependencies
RUN apk add --update git npm build-base

# prepare build dir
WORKDIR /app

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

# set build ENV
ENV MIX_ENV="prod"

# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config

# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile

COPY priv priv

COPY lib lib

COPY assets assets

# compile assets
RUN npm --prefix assets install && mix assets.deploy

# Compile the release
RUN mix compile

# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/

COPY rel rel
RUN mix release

#####################################################
# 2. Build release image
#####################################################

FROM ${RUNNER_IMAGE}

RUN apk add --update --no-cache ncurses-libs libssl1.1 openssh

# set runner ENV
ENV MIX_ENV=prod
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

WORKDIR "/app"
RUN chown nobody /app

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/diffity ./

USER nobody


EXPOSE 4000
CMD ["/app/bin/server"]
tailwind.config.js
const defaultTheme = require("tailwindcss/defaultTheme");

module.exports = {
  content: [
    "../lib/**/*.ex",
    "../lib/**/*.leex",
    "../lib/**/*.*ex",
    "./js/**/*.js",
    "./js/**/*.tsx",
    "./js/**/**/*.tsx",
  ],

  theme: {
    extend: {
      fontFamily: {
        sans: ["Poppins", ...defaultTheme.fontFamily.sans],
        serif: [...defaultTheme.fontFamily.serif],
        mono: [...defaultTheme.fontFamily.mono],
      },
    },
  },
  variants: {
    extend: {},
  },
  plugins: [require("@tailwindcss/forms")],
};
app.css
@tailwind base;
@tailwind components;
@tailwind utilities;
...

@layer components {
  .btn {
    @apply py-2 font-medium rounded shadow focus:ring-4 px-4 text-center;
  }

  .btn-blue {
    @apply text-blue-700 bg-blue-200 hover:bg-blue-300 focus:ring-blue-400;
  }
}
confix.exs
...
# Configure esbuild (the version is required)
config :esbuild,
  version: "0.14.29",
  default: [
    args: [
      "js/app.js",
      "--bundle",
      "--loader:.js=jsx",
      "--target=es2017",
      "--outdir=../priv/static/assets",
      "--external:/fonts/*",
      "--external:/images/*"
    ],
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

# Tailwind CSS
config :tailwind,
  version: "3.1.7",
  default: [
    args: [
      "--config=tailwind.config.js",
      "--input=css/app.css",
      "--output=../priv/static/assets/app.css"
    ],
    cd: Path.expand("../assets", __DIR__)
  ]
...
mix deps
{:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.1", runtime: Mix.env() == :dev},

I have made sure these suggestions from Chris are in place even though the phoenix version i use has already taken care of the changes: comment-#2 and comment-#8

When inspecting the network tab, i can confirm that the app.css file is being downloaded by the browser just fine.
image

Anyone have any debugging suggestions on this? :thinking:

have you looked in the content of the downloaded app.css, it seems quite small ?

… and why you use npm to build the assets, you have esbuild for that job ?

and btw, welcome to the forum !!

have you looked in the content of the downloaded app.css, it seems quite small ?

yea i did. :smile:
The response does not have anything related to Tailwind

and why you use npm to build the assets, you have esbuild for that job

correct. npm is only used to fetch additional packages, like React

assets are being built by esbuild itself.
I forgot to add this part in the actual question. but here is my alias for mix assets.deploy:

# mix.exs
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]

check in the root template the entry for the css inclusion and further more check if there exists more than one app.css in ./priv directory and the root template points to the wrong one. This often happens during updating a project.

check in the root template the entry for the css inclusion

yep, its already included:

# lib/*_web/templates/layout/root.html.heex
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>

from the build image, contents of the priv/ only has app.css

check if there exists more than one app.css in ./priv directory

/app $ ls lib/diffity-0.2.0/priv/static/assets/
app-469c82a27400fa9c0e39ac67e4d75104.js      app.css
app-469c82a27400fa9c0e39ac67e4d75104.js.gz   app.css.gz
app-d21dde08b813306cb995ada660dbf46e.css     app.js
app-d21dde08b813306cb995ada660dbf46e.css.gz  app.js.gz

the content of this css file however only has styles related to react-loading-skeleton. one of the additional react packages.

/app $ cat lib/diffity-0.2.0/priv/static/assets/app-d21dde08b813306cb995ada660dbf46e.css
@keyframes react-loading-skeleton{to{transform:translate ...

This often happens during updating a project.

yea, I might have missed something in between, but been over this for a few hours and still can’t figure it out :smile:

maybe, I’m not quite sure, in the app.js file there usually exists an include of the app.css file, as you use tailwind to build the final css, you have to remove it. Maybe I’m wrong, but give it a try

Well I think I missed this attention to detail.

It’s important to note that you don’t need to use a preprocessor with Tailwind — you typically write very little CSS on a Tailwind project anyways so using a preprocessor just isn’t as beneficial as it would be in a project where you write a lot of custom CSS.

Since I have some customization to tailwind, doing so would need Preprocessor I guess?

@tailwind base;
@tailwind components;
@tailwind utilities;
...

@layer components {
  .btn {
    @apply py-2 font-medium rounded shadow focus:ring-4 px-4 text-center;
  }

  .btn-blue {
    @apply text-blue-700 bg-blue-200 hover:bg-blue-300 focus:ring-blue-400;
  }
}

So i used GitHub - CargoSense/dart_sass: An installer for sass and things are working now!

It’s good that it is working now, but I think your working around the problem. Let me explain the reason why I think the app.css is doesn’t include the expected css:

  • tailwind builds app.css with tailwind default --minify
  • in the case you include the app.css in app.js, esbuild overwrites the correct app.css with the nearly empty one with command esbuild default --minify

as it works in dev, you usually do not need a Preprocessor for production build.

I don’t have it included in app.js

I stick to my opinion ;-), as you mentioned that the app.css file included just the react-loading-skeleton, the tailwind produced css is definitely overwritten by esbuild build run. try to reverse the order, first do the esbuild, then the tailwind stuff in “assets.deploy”, if you need the react-loading-skeleton css, include it in the app.css

ah ofcourse, that makes sense. That should work :slightly_smiling_face:
thanks!

yep, that part however is taken care by imports in react component(s) already.

if you reverse the order, this “import” will be overwritten by tailwind, to clean up that code, I would remove the import from the components and import all CSS related stuff in the source app.css, so that tailwind can take care of it.