CSS styles broken after launching docker-compose

Dear Friends,

Could someone please help me with the following.
We are currently trying to install Frappe LMS as an app.
We encountered a bug with CSS styles after launching docker-compose. I forked the frappe/lms repository to enable making changes to the code. However, after running the docker-compose -f /root/learning_prod_setup-compose.yml up -d command, the CSS styles were not loaded (screenshot below).
Expected result:
CSS styles should be loaded correctly from /assets/lms/dist/css/lms.bundle…css.
Steps to reproduce:
Clone the frappe/lms repository.
Modify the codebase (optional).
Update the docker-compose.yml file to use the custom repository.
Run docker-compose up.
Visit the application and inspect CSS loading issues.
Observed result:
CSS files are requested but return a 404 error. The expected files are missing in /assets/lms/dist/css.
Attempts to resolve:
I tried:
Adding {} to the assets.json file before rebuilding assets.
Running bench build to regenerate assets.
Checking permissions for the /assets directory.
Rebuilding the Docker image after changes.

Does anyone has any idea what can help?
Screenshot:


name: learning_prod_setup
services:
backend:
depends_on:
configurator:
condition: service_completed_successfully
required: true
image: ghcr.io/ph0en1xxx/lms:stable
networks:
default: null
platform: linux/amd64
pull_policy: missing
volumes:

  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    configurator:
    command:
  • |
    ls -1 apps > sites/apps.txt; bench set-config -g db_host $$DB_HOST; bench set-config -gp db_port $$DB_PORT; bench set-config -g redis_cache “redis://$$REDIS_CACHE”; bench set-config -g redis_queue “redis://$$REDIS_QUEUE”; bench set-config -g redis_socketio “redis://$$REDIS_QUEUE”; bench set-config -gp socketio_port $$SOCKETIO_PORT;
    depends_on:
    db:
    condition: service_healthy
    required: true
    redis-cache:
    condition: service_started
    required: true
    redis-queue:
    condition: service_started
    required: true
    entrypoint:
  • bash
  • -c
    environment:
    DB_HOST: db
    DB_PORT: “3306”
    REDIS_CACHE: redis-cache:6379
    REDIS_QUEUE: redis-queue:6379
    SOCKETIO_PORT: “9000”
    image: ghcr.io/ph0en1xxx/lms:stable
    networks:
    default: null
    platform: linux/amd64
    pull_policy: missing
    volumes:
  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    cron:
    command:
  • daemon
  • –docker
    depends_on:
    scheduler:
    condition: service_started
    required: true
    image: mcuadros/ofelia:latest
    networks:
    default: null
    volumes:
  • type: bind
    source: /var/run/docker.sock
    target: /var/run/docker.sock
    read_only: true
    bind:
    create_host_path: true
    db:
    command:
  • –character-set-server=utf8mb4
  • –collation-server=utf8mb4_unicode_ci
  • –skip-character-set-client-handshake
  • –skip-innodb-read-only-compressed
    environment:
    MYSQL_ROOT_PASSWORD: 123
    healthcheck:
    test:
  • CMD-SHELL
  • mysqladmin ping -h localhost --password=123
    interval: 1s
    retries: 20
    image: mariadb:10.6
    networks:
    default: null
    volumes:
  • type: volume
    source: db-data
    target: /var/lib/mysql
    volume: {}
    frontend:
    command:
  • nginx-entrypoint.sh
    depends_on:
    backend:
    condition: service_started
    required: true
    websocket:
    condition: service_started
    required: true
    environment:
    BACKEND: backend:8000
    CLIENT_MAX_BODY_SIZE: 50m
    FRAPPE_SITE_NAME_HEADER: $$host
    PROXY_READ_TIMEOUT: “120”
    SOCKETIO: websocket:9000
    UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
    UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
    UPSTREAM_REAL_IP_RECURSIVE: “off”
    image: ghcr.io/ph0en1xxx/lms:stable
    labels:
    traefik.enable: “true”
    traefik.http.routers.frontend-http.entrypoints: websecure
    traefik.http.routers.frontend-http.rule: Host(enlight.ru)
    traefik.http.routers.frontend-http.tls.certresolver: main-resolver
    traefik.http.services.frontend.loadbalancer.server.port: “8080”
    networks:
    default: null
    platform: linux/amd64
    pull_policy: missing
    volumes:
  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    proxy:
    command:
  • –providers.docker=true
  • –providers.docker.exposedbydefault=false
  • –entrypoints.web.address=:80
  • –entrypoints.web.http.redirections.entrypoint.to=websecure
  • –entrypoints.web.http.redirections.entrypoint.scheme=https
  • –entrypoints.websecure.address=:443
  • –certificatesResolvers.main-resolver.acme.httpChallenge=true
  • –certificatesResolvers.main-resolver.acme.httpChallenge.entrypoint=web
  • –certificatesResolvers.main-resolver.acme.email=a111@mail.ru
  • –certificatesResolvers.main-resolver.acme.storage=/letsencrypt/acme.json
    image: traefik:v2.11
    networks:
    default: null
    ports:
  • mode: ingress
    target: 80
    published: “80”
    protocol: tcp
  • mode: ingress
    target: 443
    published: “443”
    protocol: tcp
    volumes:
  • type: volume
    source: cert-data
    target: /letsencrypt
    volume: {}
  • type: bind
    source: /var/run/docker.sock
    target: /var/run/docker.sock
    read_only: true
    bind:
    create_host_path: true
    queue-long:
    command:
  • bench
  • worker
  • –queue
  • long,default,short
    depends_on:
    configurator:
    condition: service_completed_successfully
    required: true
    image: ghcr.io/ph0en1xxx/lms:stable
    networks:
    default: null
    platform: linux/amd64
    pull_policy: missing
    volumes:
  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    queue-short:
    command:
  • bench
  • worker
  • –queue
  • short,default
    depends_on:
    configurator:
    condition: service_completed_successfully
    required: true
    image: ghcr.io/ph0en1xxx/lms:stable
    networks:
    default: null
    platform: linux/amd64
    pull_policy: missing
    volumes:
  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    redis-cache:
    image: redis:6.2-alpine
    networks:
    default: null
    volumes:
  • type: volume
    source: redis-cache-data
    target: /data
    volume: {}
    redis-queue:
    image: redis:6.2-alpine
    networks:
    default: null
    volumes:
  • type: volume
    source: redis-queue-data
    target: /data
    volume: {}
    scheduler:
    command:
  • bench
  • schedule
    depends_on:
    configurator:
    condition: service_completed_successfully
    required: true
    image: ghcr.io/ph0en1xxx/lms:stable
    labels:
    ofelia.enabled: “true”
    ofelia.job-exec.datecron.command: bench --site all backup
    ofelia.job-exec.datecron.schedule: ‘@every 6h’
    ofelia.job-exec.datecron.user: frappe
    networks:
    default: null
    platform: linux/amd64
    pull_policy: missing
    volumes:
  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    websocket:
    command:
  • node
  • /home/frappe/frappe-bench/apps/frappe/socketio.js
    depends_on:
    configurator:
    condition: service_completed_successfully
    required: true
    image: ghcr.io/ph0en1xxx/lms:stable
    networks:
    default: null
    platform: linux/amd64
    pull_policy: missing
    volumes:
  • type: volume
    source: sites
    target: /home/frappe/frappe-bench/sites
    volume: {}
    networks:
    default:
    name: learning_prod_setup_default
    volumes:
    cert-data:
    name: learning_prod_setup_cert-data
    db-data:
    name: learning_prod_setup_db-data
    redis-cache-data:
    name: learning_prod_setup_redis-cache-data
    redis-queue-data:
    name: learning_prod_setup_redis-queue-data
    sites:
    name: learning_prod_setup_sites
    x-backend-defaults:
    depends_on:
    configurator:
    condition: service_completed_successfully
    image: ghcr.io/ph0en1xxx/lms:stable
    pull_policy: missing
    volumes:
  • sites:/home/frappe/frappe-bench/sites
    x-customizable-image:
    image: ghcr.io/ph0en1xxx/lms:stable
    pull_policy: missing
    x-depends-on-configurator:
    depends_on:
    configurator:
    condition: service_completed_successfully
1 Like

Could you try “bench build” to see if the asset files are rebuilt?

We tried bench build several times, but the result is the same(

This is a cache problem. Go into your redis container and do a ‘FLUSHALL’ command.

recreate the containers to restore the changes you did with "bench build’

I have been facing the same problem and tried recreating the containers
and did the FUSHALL as well. But still no luck

The issue was resolved, but I initially entered the container and ran bench build without knowing it could cause problems. According to the documentation, running bench build inside a production container is not necessary and can corrupt the assets.

“You cannot build assets using bench build in running production containers. It will mess up the attached assets volume for the container in which the command was executed. It will cause problems in serving static assets. The asset building process is done during the image build. Images come pre-packaged with built assets and there is no need to build assets in production environment.”

Even after removing the docker compose for ERPNext, the issue persisted. The final solution was to remove both the containers and the volumes, making sure not to delete sites, MariaDB, or Traefik.

Steps to Fix the Issue

  1. Stop and remove the ERPNext containers.
  2. Identify the Docker volumes used by ERPNext.
  3. Remove unnecessary volumes, ensuring that sites, MariaDB, and Traefik are not deleted.
  4. Recreate the containers with docker-compose.
  5. Check the logs to ensure there are no asset-related issues.

By following this process, the issue with static assets was resolved without the need to manually run bench build. This approach ensures the proper handling of assets as per the intended Docker workflow for ERPNext.

Hope this help

1 Like