Hello everyone,
After more than 6 months working on my ERPNext project, I would like some architectural clarification before continuing further UI customization.
Initially, I deployed ERPNext using Docker Desktop on Windows. During that phase, I encountered severe asset-related instability:
Persistent 404 errors on hashed CSS/JS bundles
Backend generating new bundle hashes while Nginx served outdated ones
Volume persistence conflicts in Docker
Permission corruption when interacting with assets from Windows
Git repository inconsistencies blocking bench build
Redis/DB hostname mismatches after restoring backups
Container restart loops due to service resolution failures
Disk saturation from repeated rebuilds
In multiple cases, the only solution was removing Docker volumes entirely and rebuilding from scratch.
Due to the instability, I migrated away from Docker and deployed directly on Ubuntu in a VPS. The system is currently stable with backups in place.
However, I am now hesitant to perform deeper UI modifications.
My main goal is not just styling tweaks β I would like to:
Adjust layout structure
Remove certain UI elements
Inject custom JS behavior
Slightly restructure parts of the Desk
Before proceeding further, I would appreciate clarification on:
Is structural UI modification considered supported practice?
Are there architectural limitations in the Desk (Vue/frontend bundle) that make deep customization risky?
What is the recommended workflow for projects that require non-trivial frontend adjustments?
Should such changes always be isolated in a separate development instance?
After the Docker asset conflicts, I want to ensure I am working within the intended architectural boundaries of Frappe, rather than fighting against the framework.
Any guidance would be greatly appreciated.
Thank you.
1 Like
opened 06:49AM - 08 Oct 25 UTC
closed 06:19PM - 10 Dec 25 UTC
bug
### Information about bug
Hello, I have ERPNext installed using the templates iβ¦ nside Dokploy. The instance works perfectly at first, but after about 5β15 days, all the assets (CSS and JS) suddenly stop working. This has happened three times now. After installation, everything runs fine for a while, then the assets fail to load, and I have to reinstall which is very frustrating.
Here are my observations:
The website requests these asset files:
website.bundle.NSZNBQCJ.css
login.bundle.IGORFKYS.css
frappe-web.bundle.4QVP5SVA.js
frappe-web.bundle.4QVP5SVA.js
However, the assets.json file points to completely different file names.
```json
frappe@31efd83389d8:/$ cat /home/frappe/frappe-bench/sites/assets/assets.json
{
"billing.bundle.js": "/assets/frappe/dist/js/billing.bundle.QQE2RPCA.js",
"bootstrap-4-web.bundle.js": "/assets/frappe/dist/js/bootstrap-4-web.bundle.FOZOVELL.js",
"controls.bundle.js": "/assets/frappe/dist/js/controls.bundle.NZC3GXOU.js",
"data_import_tools.bundle.js": "/assets/frappe/dist/js/data_import_tools.bundle.IGMLQRF6.js",
"desk.bundle.js": "/assets/frappe/dist/js/desk.bundle.IMPJIRYH.js",
"dialog.bundle.js": "/assets/frappe/dist/js/dialog.bundle.YHWHYNFQ.js",
"form.bundle.js": "/assets/frappe/dist/js/form.bundle.7PHQTBOA.js",
"frappe-web.bundle.js": "/assets/frappe/dist/js/frappe-web.bundle.5RBX33XN.js",
"libs.bundle.js": "/assets/frappe/dist/js/libs.bundle.TIV7ZGVY.js",
"list.bundle.js": "/assets/frappe/dist/js/list.bundle.ET452RHV.js",
"logtypes.bundle.js": "/assets/frappe/dist/js/logtypes.bundle.EKN7LWKW.js",
"onboarding_tours.bundle.js": "/assets/frappe/dist/js/onboarding_tours.bundle.RAUR6X4Z.js",
"report.bundle.js": "/assets/frappe/dist/js/report.bundle.AMKILTJH.js",
"sentry.bundle.js": "/assets/frappe/dist/js/sentry.bundle.AX44GVWW.js",
"telemetry.bundle.js": "/assets/frappe/dist/js/telemetry.bundle.LKEZCADB.js",
"user_profile_controller.bundle.js": "/assets/frappe/dist/js/user_profile_controller.bundle.TPZWXYWN.js",
"video_player.bundle.js": "/assets/frappe/dist/js/video_player.bundle.DUYYLSFO.js",
"web_form.bundle.js": "/assets/frappe/dist/js/web_form.bundle.5DVA6C7G.js",
"form_builder.bundle.js": "/assets/frappe/dist/js/form_builder.bundle.CYK52TRC.js",
"print_format_builder.bundle.js": "/assets/frappe/dist/js/print_format_builder.bundle.3YCNST3U.js",
"workflow_builder.bundle.js": "/assets/frappe/dist/js/workflow_builder.bundle.3GDVTW3J.js",
"build_events.bundle.js": "/assets/frappe/dist/js/build_events.bundle.BCX32TWL.js",
"file_uploader.bundle.js": "/assets/frappe/dist/js/file_uploader.bundle.IO2BUJDB.js",
"kanban_board.bundle.js": "/assets/frappe/dist/js/kanban_board.bundle.FBBHCLAN.js",
"desk.bundle.css": "/assets/frappe/dist/css/desk.bundle.YEWO6UTN.css",
"email.bundle.css": "/assets/frappe/dist/css/email.bundle.NAP7GUIG.css",
"login.bundle.css": "/assets/frappe/dist/css/login.bundle.EEV2YCSU.css",
"print.bundle.css": "/assets/frappe/dist/css/print.bundle.5UWPJEM3.css",
"print_format.bundle.css": "/assets/frappe/dist/css/print_format.bundle.NVW3TB2Z.css",
"report.bundle.css": "/assets/frappe/dist/css/report.bundle.GC4I5GYW.css",
"web_form.bundle.css": "/assets/frappe/dist/css/web_form.bundle.7LGJLN7Y.css",
"website.bundle.css": "/assets/frappe/dist/css/website.bundle.QFVSUF2X.css",
"bank-reconciliation-tool.bundle.js": "/assets/erpnext/dist/js/bank-reconciliation-tool.bundle.PQSY2TRN.js",
"erpnext-web.bundle.js": "/assets/erpnext/dist/js/erpnext-web.bundle.J4A2DQB4.js",
"erpnext.bundle.js": "/assets/erpnext/dist/js/erpnext.bundle.F4VLHMIR.js",
"item-dashboard.bundle.js": "/assets/erpnext/dist/js/item-dashboard.bundle.33GEOV2J.js",
"point-of-sale.bundle.js": "/assets/erpnext/dist/js/point-of-sale.bundle.JTJY4AB2.js",
"bom_configurator.bundle.js": "/assets/erpnext/dist/js/bom_configurator.bundle.VB3HBFNX.js",
"erpnext-web.bundle.css": "/assets/erpnext/dist/css/erpnext-web.bundle.6B3MDDZU.css",
"erpnext.bundle.css": "/assets/erpnext/dist/css/erpnext.bundle.JQ7CB7T3.css",
"erpnext_email.bundle.css": "/assets/erpnext/dist/css/erpnext_email.bundle.47GDA6MO.css"
}
```
Also, the /assets directory actually contains the files listed in assets.json, but the website still tries to load the wrong ones.
This is the compose file from the template
```yaml
x-custom-image: &custom_image
image: ${IMAGE_NAME:-docker.io/frappe/erpnext}:${VERSION:-version-15}
pull_policy: ${PULL_POLICY:-always}
deploy:
restart_policy:
condition: always
services:
backend:
<<: *custom_image
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- '0.0.0.0:8000'
interval: 2s
timeout: 10s
retries: 30
frontend:
<<: *custom_image
command:
- nginx-entrypoint.sh
depends_on:
backend:
condition: service_started
required: true
websocket:
condition: service_started
required: true
environment:
BACKEND: backend:8000
FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
SOCKETIO: websocket:9000
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
UPSTREAM_REAL_IP_RECURSIVE: "off"
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- '0.0.0.0:8080'
interval: 2s
timeout: 30s
retries: 30
queue-default:
<<: *custom_image
command:
- bench
- worker
- --queue
- default
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
depends_on:
configurator:
condition: service_completed_successfully
required: true
queue-long:
<<: *custom_image
command:
- bench
- worker
- --queue
- long
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
depends_on:
configurator:
condition: service_completed_successfully
required: true
queue-short:
<<: *custom_image
command:
- bench
- worker
- --queue
- short
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
depends_on:
configurator:
condition: service_completed_successfully
required: true
scheduler:
<<: *custom_image
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
command:
- bench
- schedule
depends_on:
configurator:
condition: service_completed_successfully
required: true
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
websocket:
<<: *custom_image
healthcheck:
test:
- CMD
- wait-for-it
- '0.0.0.0:9000'
interval: 2s
timeout: 10s
retries: 30
command:
- node
- /home/frappe/frappe-bench/apps/frappe/socketio.js
depends_on:
configurator:
condition: service_completed_successfully
required: true
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
configurator:
<<: *custom_image
deploy:
mode: replicated
replicas: ${CONFIGURE:-0}
restart_policy:
condition: none
entrypoint: ["bash", "-c"]
command:
- >
[[ $${REGENERATE_APPS_TXT} == "1" ]] && ls -1 apps > sites/apps.txt;
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && exit 0;
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;
environment:
DB_HOST: "${DB_HOST:-db}"
DB_PORT: "3306"
REDIS_CACHE: redis-cache:6379
REDIS_QUEUE: redis-queue:6379
SOCKETIO_PORT: "9000"
REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
create-site:
<<: *custom_image
deploy:
mode: replicated
replicas: ${CREATE_SITE:-0}
restart_policy:
condition: none
entrypoint: ["bash", "-c"]
command:
- >
wait-for-it -t 120 $$DB_HOST:$$DB_PORT;
wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue:6379;
export start=`date +%s`;
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
do
echo "Waiting for sites/common_site_config.json to be created";
sleep 5;
if (( `date +%s`-start > 120 )); then
echo "could not find sites/common_site_config.json with required keys";
exit 1
fi
done;
echo "sites/common_site_config.json found";
[[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0;
bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME};
volumes:
- sites:/home/frappe/frappe-bench/sites
environment:
SITE_NAME: ${SITE_NAME}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DB_HOST: ${DB_HOST:-db}
DB_PORT: "${DB_PORT:-3306}"
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
networks:
- bench-network
migration:
<<: *custom_image
deploy:
mode: replicated
replicas: ${MIGRATE:-0}
restart_policy:
condition: none
entrypoint: ["bash", "-c"]
command:
- >
curl -f http://${SITE_NAME}:8080/api/method/ping || echo "Site busy" && exit 0;
bench --site all set-config -p maintenance_mode 1;
bench --site all set-config -p pause_scheduler 1;
bench --site all migrate;
bench --site all set-config -p maintenance_mode 0;
bench --site all set-config -p pause_scheduler 0;
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
db:
image: mariadb:10.6
deploy:
mode: replicated
replicas: ${ENABLE_DB:-0}
restart_policy:
condition: always
healthcheck:
test: mysqladmin ping -h localhost --password=${DB_ROOT_PASSWORD}
interval: 1s
retries: 20
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
- --skip-innodb-read-only-compressed
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
volumes:
- db-data:/var/lib/mysql
networks:
- bench-network
redis-cache:
deploy:
restart_policy:
condition: always
image: redis:6.2-alpine
networks:
- bench-network
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 5s
retries: 3
redis-queue:
deploy:
restart_policy:
condition: always
image: redis:6.2-alpine
volumes:
- redis-queue-data:/data
networks:
- bench-network
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 5s
retries: 3
redis-socketio:
deploy:
restart_policy:
condition: always
image: redis:6.2-alpine
volumes:
- redis-socketio-data:/data
networks:
- bench-network
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 5s
retries: 3
volumes:
db-data:
redis-queue-data:
redis-socketio-data:
sites:
driver_opts:
type: "${SITE_VOLUME_TYPE}"
o: "${SITE_VOLUME_OPTS}"
device: "${SITE_VOLUME_DEV}"
networks:
bench-network:
```
Things Iβve already tried:
bench build
bench restart
bench clear-cache
bench clear-website-cache
Verified file permissions belong to the frappe user
Cleared and disabled Redis cache
Removed Redis cache volume
Restarted the machine
None of these helped.
Iβve checked every related discussion on the forums, but I still canβt find the root cause. I need to understand why this keeps happening and how to apply a permanent fix. Thanks
### Module
accounts, other
### Version
version-15
### Installation method
None
### Relevant log output / Stack trace / Full Error Message.
```shell
```
Itβs a deployment issue.
Pejay
February 27, 2026, 5:24am
3
Did you already check the permission about your assets directories? I have encounter the same issue it was just all about the permission.
here is the script to give full permission to asset files
cd /path/to/frappe-bench/sites
sudo chmod -R 777 assets