nickjj / docker-flask-example

A production ready example Flask app that's using Docker and Docker Compose.
MIT License
583 stars 101 forks source link

assistance understanding assets and es build in production #22

Open webdz9r opened 2 months ago

webdz9r commented 2 months ago

Hi Nick,

It's me again, I'm new to ES build / Flask and fancy node based frontend stuff...

I've got an app in production and I'm seeing my logs continuesly state that it's rebuilding CSS and JS


css-1    |
css-1    | Done in 1439ms.
css-1    |
css-1    | Task completed in 0m2.511s
css-1    | Browserslist: caniuse-lite is outdated. Please run:
css-1    |   npx update-browserslist-db@latest
css-1    |   Why you should do it regularly: https://github.com/browserslist/update-db#readme
css-1    |
css-1    | Rebuilding...
css-1    |
css-1    | Done in 1526ms.
css-1    |
css-1    | Task completed in 0m2.515s
js-1     |
js-1     | Task completed in 0m0.352s
css-1    | Browserslist: caniuse-lite is outdated. Please run:
css-1    |   npx update-browserslist-db@latest
css-1    |   Why you should do it regularly: https://github.com/browserslist/update-db#readme
css-1    |
css-1    | Rebuilding...
css-1    |
css-1    | Done in 1534ms.
css-1    |
css-1    | Task completed in 0m2.572s```

I thought in production this was to be build once and not again, it seems the `production` and debug flags are a bit off too, when I put things in production I still had to modify the docker container to set the values to `False` vs `false` in the config.settings.py

Any advise on better understand prod vs dev? 
nickjj commented 2 months ago

Hi,

This is documented in the .env file with a comment: https://github.com/nickjj/docker-flask-example/blob/93af9f4fbf185098ffb1d120ee0693abcd77a38b/.env.example#L15

For production mode, you'll want to make sure all 4 of these are set:

export COMPOSE_PROFILES=postgres,redis,web,worker
export DOCKER_WEB_VOLUME=./public:/app/public
export FLASK_DEBUG=false
export NODE_ENV=production

Technically the last 2 are the default values, so by not setting them in the .env file you get the same effect.

Once you've done that then you can docker compose build to build your image that'll have assets precompiled and then they will be served in production directly without tailwind and esbuild running when you next docker compose up the project. Each asset file will also be digested too with an md5 checksum which you can verify by viewing the source of the page.

webdz9r commented 2 months ago

Hi Nick,

thanks for the response. In production I was getting the debug toolbar until I changed config/settings.py to use strict values

-SECRET_KEY = os.getenv("SECRET_KEY", "supersecret")
-DEBUG = bool(strtobool(os.getenv("FLASK_DEBUG", "false")))
-PROFILE = bool(strtobool(os.getenv("FLASK_PROFILE", "false")))

+SECRET_KEY = "xxxxxxx"
+
+DEBUG = False

Maybe it was a cached build problem, I'm not sure

nickjj commented 2 months ago

Hmm you can always run docker compose build --no-cache to get a fresh build without cache coming into play.

webdz9r commented 2 months ago

is it normal to have the JS and CSS rebuild like that in the background - I assume that means it's rebuilding something and configuration is not right

nickjj commented 2 months ago

is it normal to have the JS and CSS rebuild like that in the background

In my previous comment I mentioned setting export COMPOSE_PROFILES=postgres,redis,web,worker , did you set that? That would prevent tailwind and esbuild from running.

Once you've done that then you can docker compose build to build your image that'll have assets precompiled and then they will be served in production directly without tailwind and esbuild running when you next docker compose up the project. Each asset file will also be digested too with an md5 checksum which you can verify by viewing the source of the page.

webdz9r commented 2 months ago

I have the following set as I'm not using celery or postgres at the moment export COMPOSE_PROFILES=redis,assets,web

nickjj commented 2 months ago

In that case the JS and CSS won't get recompiled or run in the background. They are precompiled once at Docker image build time and then are served on each request by either gunicorn or nginx if you add that into that mix.

webdz9r commented 2 months ago

could you help me understand the function for this variable FLASK_SKIP_DOTENV="true" \

Seems that we always want env variables?

nickjj commented 2 months ago

Yes, that stemmed from this issue: https://github.com/pallets/flask/issues/3872

Docker Compose already makes environment variables available without needing the dotenv Python package. Without setting that env var then all flask CLI commands throw a warning saying to use doteven even if you don't need it. The current maintainer of Flask didn't want to disable that setting by default so we set that environment variable.

webdz9r commented 2 months ago

Hi Nick thanks again for working with me on these items.

I'm having the worst time getting my production configurations working properly and missing CSS / JS modules.

If I wanted to install Preline and tailwind typography / forms - wouldn't I just follow the install docs provided for each?

Seems the pipelines are not properly adding those packages and I'm having to include the CDN version inside my main template to get them working.

You might be thinking... "this is basic" and that's because I don't understand nodes role and asset compiling when it comes to packaging - I'm hoping for some quick tips to point me in the right direction for configuration and understanding as it relates to using flask *(this project) as a base

in the case of praline it's stating to include this in your template ''

but I'm not seeing any reference to ./node_modules/ folder generated on web container

webdz9r commented 2 months ago

another item - when trying to add tailwind/forms

I've added this to my Dockerfile for the asset pipeline RUN npm install -D @tailwindcss/forms

edit my tailwind.config.js

module.exports = {
  content: [
    '/app/assets/js/**/*.js',
    '/app/assets/css/**/*.css',
    '/app/teetime/**/*.html',
    './node_modules/preline/dist/*.js'
  ],
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('preline/plugin')
  ],
  darkMode: 'class'
}

when I run docker compose build and docker compose up

web-1    | [2024-05-08 16:06:51 +0000] [1] [INFO] Starting gunicorn 21.2.0
web-1    | [2024-05-08 16:06:51 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
web-1    | [2024-05-08 16:06:51 +0000] [1] [INFO] Using worker: sync
web-1    | [2024-05-08 16:06:51 +0000] [7] [INFO] Booting worker with pid: 7
css-1    | 
css-1    | Rebuilding...
css-1    | Error: Cannot find module '@tailwindcss/forms'
css-1    | Require stack:
css-1    | - /app/assets/tailwind.config.js
css-1    |     at Module._resolveFilename (node:internal/modules/cjs/loader:1048:15)
css-1    |     at Function.resolve (node:internal/modules/helpers:136:19)
css-1    |     at _resolve (/node_modules/jiti/dist/jiti.js:1:251148)
css-1    |     at jiti (/node_modules/jiti/dist/jiti.js:1:253746)
css-1    |     at /app/assets/tailwind.config.js:9:5
css-1    |     at evalModule (/node_modules/jiti/dist/jiti.js:1:256443)
css-1    |     at jiti (/node_modules/jiti/dist/jiti.js:1:254371)
css-1    |     at /node_modules/tailwindcss/lib/lib/load-config.js:48:30
css-1    |     at loadConfig (/node_modules/tailwindcss/lib/lib/load-config.js:50:6)
css-1    |     at Object.loadConfig (/node_modules/tailwindcss/lib/cli/build/plugin.js:135:49) {
css-1    |   code: 'MODULE_NOT_FOUND',
css-1    |   requireStack: [ '/app/assets/tailwind.config.js' ]
css-1    | }
nickjj commented 2 months ago

Hi,

You don't need to add the RUN instruction. You can run ./run yarn:install after adding the dependency to your package.json file and it will take care of installing any new dependencies and also write out a new lock file. The readme file has this documented: https://github.com/nickjj/docker-flask-example?tab=readme-ov-file#updating-dependencies

webdz9r commented 2 months ago

does this behavior change from production vs development?

nickjj commented 2 months ago

Yes, that's also mentioned in the docs and blog posts linked in the readme file.

In production assets will get digested and saved in a spot so you can reference them from a web server without needing to run the css or js containers.

webdz9r commented 2 months ago

I'm so close to getting this all sorted out...

I'm trying to install preline

yarn is doing it's thing properly but it seems that flask needs the /node_module/preline/dist/*.js files in order to work. What is the proper way to make those files available inside the application container. Seems that docker is only coping the /app/public folder

webdz9r commented 2 months ago

Here is what I was able to come up with (doesn't feel very gitops) in the Dockerfile for the app layer

## copy specific node_modules
# preline
COPY --from=assets --chown=python:python /node_modules/preline/dist/*.js /public/js/preline/

From there my package.json includes now work

    "tailwindcss": "3.4.1",
    "preline": "2.1.0",
    "@tailwindcss/forms": "0.5.7",
    "@tailwindcss/typography": "0.5.13"

and it's comparable with my tailwind.config.js

module.exports = {
  content: [
    '/app/assets/js/**/*.js',
    '/app/assets/css/**/*.css',
    '/app/teetime/**/*.html'

  ],
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('preline/plugin')
  ],
  darkMode: 'class'
}

hopefully it helps and I welcome feedback on how to make it more turnkey / automatic

nickjj commented 2 months ago

Hi, I never used that package. I'm not super well versed with esbuild but I wonder if there's anything you can do or configure to have it copy those files to a destination directory so you don't need to modify your Dockerfile.