Deployed Phoenix app does not honor css instructions

Finally, I did get everything right and managed to deploy my app (to be) in a dokku setup on one of my own servers - with bells and whistles, letsencrypt, and what not :smile:

Alas my moment of joy only lasted until I browsed my way to stage2.speicher.ltd :cry:

I hurried to verify that all ‘gets’ had status=200 - which they have (bar a couple of pics but that’s beside the point)

Then I did a local mix release (in fact I did this )

#!/bin/sh

mix do deps.get, deps.compile
npm install --prefix assets && \
  npm rebuild node-sass --prefix assets && \
  npm --prefix ./assets ci --progress=false --no-audit --loglevel=error

npm run --prefix ./assets deploy
mix phx.digest

NODE_ENV=prod \
  MIX_ENV=prod \
  APP_PORT=5000 \
  POOL_SIZE=10 \
  COOL_TEXT='tjuhej' \
  SECRET_KEY_BASE=xxx \
  DBUSER='xxx' \
  DBPWRD='xxx' \
  DBNAME='xxx' \
  DBHOST="xxx" \
  URLHOST='localhost' \
mix do compile, release

NODE_ENV=prod \
  MIX_ENV=prod \
  APP_PORT=5000 \
  POOL_SIZE=10 \
  COOL_TEXT='tjuhej' \
  SECRET_KEY_BASE=xxx \
  DBUSER='xxx' \
  DBPWRD='xxx' \
  DBNAME='xxx' \
  DBHOST="xxx" \
  URLHOST='localhost' \
_build/prod/rel/fish/bin/fish start

And sure enough everything was layed out with CSS as should be :face_with_thermometer:

Then I did docker exec -it 59dde6cc7690 /bin/bash on the host and looked at the css and js files in the build folder with ls -la priv/static/css && ls -la priv/static/js which provided me with

total 40
drwxr-xr-x    1 nobody   nobody        4096 Jun 19 08:17 .
drwxr-xr-x    1 nobody   nobody        4096 Jun 19 08:17 ..
-rw-r--r--    1 nobody   nobody       10309 Jun 19 08:17 app-ada34c09c604a70f521cfb3887f52835.css
-rw-r--r--    1 nobody   nobody        2775 Jun 19 08:17 app-ada34c09c604a70f521cfb3887f52835.css.gz
-rw-r--r--    1 nobody   nobody       10309 Jun 19 08:17 app.css
-rw-r--r--    1 nobody   nobody        2775 Jun 19 08:17 app.css.gz
total 240
drwxr-xr-x    1 nobody   nobody        4096 Jun 19 08:17 .
drwxr-xr-x    1 nobody   nobody        4096 Jun 19 08:17 ..
-rw-r--r--    1 nobody   nobody       85163 Jun 19 08:17 app-724019b72e83ce3178e2f240205ce182.js
-rw-r--r--    1 nobody   nobody       23286 Jun 19 08:17 app-724019b72e83ce3178e2f240205ce182.js.gz
-rw-r--r--    1 nobody   nobody       85163 Jun 19 08:17 app.js
-rw-r--r--    1 nobody   nobody          98 Jun 19 08:17 app.js.LICENSE-bcda1cd32249233358d1702647c75e56.txt
-rw-r--r--    1 nobody   nobody         108 Jun 19 08:17 app.js.LICENSE-bcda1cd32249233358d1702647c75e56.txt.gz
-rw-r--r--    1 nobody   nobody          98 Jun 19 08:17 app.js.LICENSE.txt
-rw-r--r--    1 nobody   nobody         108 Jun 19 08:17 app.js.LICENSE.txt.gz
-rw-r--r--    1 nobody   nobody       23286 Jun 19 08:17 app.js.gz

So, I thought - cache! Then I did browse stage2.speicher.ltd from a ‘new’ device but sadly the result is the same!

Have you got any clue what-so-ever then please share - I’m completely baffled here :slightly_frowning_face:

I assume that the problem is that your website is missing the expected style, correct?

At a first look it seems that your CSS bundle, https://stage2.speicher.ltd/css/app-ada34c09c604a70f521cfb3887f52835.css, does not contain all the rules that one would expect looking at your HTML. For example, there are no rules for the HTML classes max-w-screen-xl, mx-auto, and many of those framework classes that you use.

In other words your assets are correctly compiled and served, but they miss some expected content. It’s hard to guess why without looking at your code, but first of all I would look into your assets to make sure that all the necessary CSS is included.

1 Like

True - styling is off

the question is more like: if I do a (local) mix release everything is in place and all CSS rules are honored - but without touching anything - when I do (the same) mix release, only now in a container (which is what technically is happening in a dokku deploy) the css is less than half the size!!

-rw-r--r--   1 walther  staff    28898 19 Jun 10:25 app-e8d95eb985318b44f69a005c82e5464e.css
-rw-r--r--   1 walther  staff     6168 19 Jun 10:25 app-e8d95eb985318b44f69a005c82e5464e.css.gz

vs the docker

-rw-r--r--    1 nobody   nobody       10309 Jun 19 08:17 app-ada34c09c604a70f521cfb3887f52835.css
-rw-r--r--    1 nobody   nobody        2775 Jun 19 08:17 app-ada34c09c604a70f521cfb3887f52835.css.gz

:slightly_frowning_face:

I’m going hunting in a dokku forum - this has obviously nothing to do with neither Elixir, nor Phoenix, but has to be a dokku thing of sorts!

If I do a local docker-compose up I bring my container alive on my own machine - with the “true” filesize of the bundled css, hence it has to be dokku that somehow fiddles with whatever part of the package :angry:

But I will report back onec I get this sorted out!

1 Like

Interesting, curious to know what you will find out :slight_smile:

The release and assets are compiled inside docker right?

It may be a longshot but i would double check for any .dockerignore that might be around filtering out some needed files

Not s bad shot at all!

But my local build does exactly the same (with a happy result) - using the same .dockerignore

  • but I’ll certainly look into that option

Thx

If you try it locally, but from a fresh repository clone, do you still get the correct result?

It could be a .gitignore issue :thinking:

This sounds a bit like an over-aggressive PurgeCSS (https://tailwindcss.com/docs/controlling-file-size/#setting-up-purgecss-manually).

If you’re using PurgeCSS, maybe try removing it and see if it improves things?

2 Likes

You’re most welcome to validate my findings - I have made it a public repo at https://github.com/wdiechmann/fish.git

(I tried pulling af fresh copy - no luck - even if it was a perfectly valid idea)

thx for caring and for your interest in my predicament <3

“you hit me right in the pantalone, partener” as Hrundi V Bakshi a.k.a Peter Sellers would have said :wink:

I’m in no way what so ever fluent in Webpack & Friends - operating on a strict “monkey orders” command by command.

I remember seeing PurgeCSS when I was meddling with Tailwind in order to get it glued into the project in the first place - but either I didn’t make it to the “steamy finale” or I dodged it, sorry cannot recall.

But the entire repo is at https://github.com/wdiechmann/fish.git - and I’d appreciate it a lot if anyone would spend a moment trying to help me push through this :face_with_thermometer:

I don’t know this stuff well either, but you could try removing these lines and see how you do :slight_smile:

https://github.com/wdiechmann/fish/blob/91ab23a3e6ea8605a572c0b1f7c64bee3139adb4/assets/tailwind.config.js#L4-L10

Relevant docs: https://tailwindcss.com/docs/controlling-file-size/

well - perhaps I will - but maybee not :smile:

'cause I’ve made quite a breakthrough - and I’m hurrying to document it and will post a link here

thx for your attention and interest - really appreciate it!

Don’t know which of your replies helped me the most - I am so moved by your attention and interest!

I’ve tried to collect my observations and experiences with this small ‘test’ in a blog post like thing at https://cunnin.gs/2-weeks-of-elixir-phoenix-docker-hell/

It’s a WIP I’m afraid - but I hope to eventually have a “plug-and-play” basic Elixir/Phoenix repo for others to enjoy pulling when in need!

Once again - thank you from the bottom of my heart!

2 Likes

Just to let you know, I’m responsible for the latest Dockerfile that’s in the phoenix releases documentation. So you can blame me if it doesn’t work :wink:

I just tried it again to see if anything broke but it works fine for me.

For it to work you need the following though:

  1. Have a proper config (url, server: true for phoenix to start and such)
  2. To test it in docker on a mac/windows machine you might have to disable the :inet6 transport option because it seems only docker on linux supports ipv6
  3. rename my_app to your app name in the Dockerfile

I looked at your Dockerfile and here are some tips:

  • Don’t use COPY . .. Always be as specific as possible. Docker makes heavy use of caching layers. For example, let’s say you edit a README.md file in the root of your project. That file doesn’t have anything to do with the build itself. But because you used COPY . ., which includes README.md, you’ve invalidated the cache. That means every step after the COPY . . will be executed again even if you didn’t change anything else. If your .dockerignore isn’t configured properly you can have some really hard to trace problems. You might copy things that are compiled for your mac into the linux container for instance. Yes, I’ve seen that happen :slight_smile:
  • If you have multiple RUN statements after each other, combine them into 1 run statement. Each RUN statement causes the creation of a new layer. For example, RUN chown -R nobody:nobody /app basically creates a copy of all the files in the app directory, just with new permissions. The originals aren’t changed and are still in the previous layer. Your image file just grew a lot in size, even though it’s “just” a permission change.
  • Try to separate the run/copy statements for elixir files and the assets. Because the COPY . . is at the top, all the npm commands are executed again, even when you only change elixir files.
  • Use multistage Dockerfiles. Right now you have only 1 FROM. This means that ALL build dependencies, node_modules, source and such end up in your final image even though you don’t need them to run your application. Your final image is 576Mb. When using the Dockerfile in the release docs for a new phoenix project (without db) you end up with an image around 22Mb in size. A huge difference. With multistage files you can separate the image/files you need for your build from the minimal image you need to just run the application.

This is basically why the Dockerfile in the release docs is the way it is. It’s all about optimising for caching (everything for the build itself) and size (the final image that runs in production).

You can optimise even further using buildkit for instance but that makes the Dockerfile a bit more complex so I didn’t include that in the docs.

3 Likes

The way PurgeCSS works is the following:

  1. Does a regex search through the paths it was given and collects all the class names it found in the files
  2. Strips all definitions in your CSS files that was not found in step 1.

PurgeCSS can break your builds in a few ways:

  1. The regex definition you use does not capture all class names
  2. You did not point it to all the files containing references to the CSS classes
  3. You run PurgeCSS when the HTML/LiveView/View files aren’t present.

Looking at your Dockerfile: https://github.com/wdiechmann/fish/blob/91ab23a3e6ea8605a572c0b1f7c64bee3139adb4/Dockerfile

I’m pretty sure what happened was 3.

At line 38 you run npm deploy. Which runs PurgeCSS.

However, you only add the HTML files actually referencing the CSS classes into your Dockerimage at line 42

Meaning when PurgeCSS ran it didn’t see any CSS classes being used anywhere. So it just went ahead and empties your entire CSS file.

Edit

I’ve uploaded the Dockerfile I use for my template repo that works with Phoenix + TailwindCSS + PurgeCSS

3 Likes

I updated my https://cunnin.gs/2-weeks-of-elixir-phoenix-docker-hell/ trying to clarify that ‘your’ Dockerfile in the docs is fine - it just did not help me :cry:

On the topic of multistage - oh I’d like that but Dokku complains about missing intermediates because it does a (too) wonderful job of cleaning up the build stage intermediates - and I haven’t solved how to keep them long enough to utilize multistage :frowning:

that Dockerfile is a marvel!

If only I could use it - but Dokku woun’t let me use multistage :cry:

2 Likes

@wdiechmann Ah, I don’t know anything about Dokku but if it’s really the case that it doesn’t support multistage builds I’d consider that a serious red flag. It’s still a single Dockerfile and single docker build so there shouldn’t be any issues. Multistage is one of the most important docker features to me.

@praveenperera nice clean Dockerfile. I’d probably create a single base for both the deps-getter and release-builder steps since they both do the same apk installs. You shouldn’t need the mkdir commands. WORKDIR already creates the directory if it doesn’t exist and most others should be created automatically as well I believe. And I’d probably combine multiple COPY and multiple RUN statements wherever possible. It’s easier to read now though and doesn’t make too much of a difference anyway since they aren’t in the final image.

1 Like

What version of Dokku are you using? I thought this problem was fixed with this PR:

1 Like