Skip to content

Custom app installed via pip install -e is missing the sites/assets/<app> symlink that bench get-app creates #1712

Description

@oysi2025

Context

When packaging custom Frappe apps in a Docker image, bench get-app is awkward to use because it requires network access at build time and isn't idempotent for purely local code. The natural alternative — and what every "custom app in Docker" tutorial converges on — is:

COPY apps/oysi_compliance /home/frappe/frappe-bench/apps/oysi_compliance
RUN /home/frappe/frappe-bench/env/bin/pip install -e /home/frappe/frappe-bench/apps/oysi_compliance

This installs the Python package correctly. The app is importable, hooks fire, DocTypes get created on bench migrate, and bench list-apps shows it.

But the sites/assets/<app> symlink is silently missing, and the asset pipeline relies on it for serving static files (<app>/<app>/public/). bench get-app creates this symlink as part of its install flow; pip install -e does not.

Symptom

Custom JS/CSS bundles in <app>/<app>/public/css/... and <app>/<app>/public/js/... return 404 from the frontend. From the user's perspective the app is "installed" — only the assets are silently broken.

Reproduction

  1. Base image: frappe/erpnext:v16
  2. Add a custom app via COPY + pip install -e (as above), with a <app>/<app>/public/css/<app>.bundle.css referenced via app_include_css hook.
  3. Build and start the bench, run bench migrate, request the desk in a browser.
  4. Network tab shows 404 for assets/<app>/css/<app>.bundle.css.
  5. Inspect: ls -la /home/frappe/frappe-bench/sites/assets/ — missing <app> symlink.
  6. Manual fix: ln -s ../../apps/<app>/<app>/public sites/assets/<app>. Assets load immediately.

We hit this with three local custom apps in a row (oysi_compliance, ecommerce_integrations, oysi_branding); each one cost a debug round before the pattern clicked.

Suggested fix

In order of increasing scope:

  1. Doc note in the "Custom Apps in Docker" section: explicitly call out that pip install -e skips the asset symlink, and provide the one-line ln -s fix or recommend a wrapper.
  2. bench symlink-app-assets <app> subcommand — idempotent, creates the symlink if missing. Easy to drop into a Dockerfile (RUN bench symlink-app-assets oysi_compliance).
  3. Refactor bench get-app so the symlink creation lives in its own internal function that can be invoked independently from the network-fetch path.

(2) feels like the sweet spot — small surface, fixes the docker-packaging case cleanly without touching get-app semantics.

Environment

  • frappe/erpnext:v16 base image (Frappe 16.13, ERPNext 16.12)
  • Three local custom apps installed via COPY + pip install -e
  • Symptom and fix verified on production deployment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions