Tutor [Palm]: How to bind-mount a local MFE folder

Hi Adolfo, thanks for your reply. Perhaps we need to break this out into a separate topic, something like “Tutor [Palm]: How to bind-mount a local MFE folder”

I have tried a few different approaches to get this working successfully, and have had no luck… yet.

tutor config save --append MOUNTS=/path/to/my/local/mfe/profile
tutor dev launch

and also

tutor mounts add /path/to/my/local/mfe/profile
tutor dev launch

In both instances, I see my config.yml has the following:

MOUNTS:
- /path/to/my/local/mfe/profile

tutor mounts list shows:

- name: /path/to/my/local/mfe/profile
  build_mounts: []
  compose_mounts: []

But every time I launch dev, the profile page does not show any of my local changes.

What am I missing?

Your first option is the correct one, and should’ve worked as per the documentation.

Can you maybe post the launch logs on a gist or somewhere?

Also, what version of Tutor are you running?

Hi @arbrandes thanks for your quick response. I’m running Tutor 16.0.2.

I figured out why it wasn’t working for me - when I created the MFE forks for my org, I changed the name of the fork to something other than frontend-app-profile, etc. If the name doesn’t exactly match the upstream repo letter-for-letter, word-for-word, the bind-mount simply won’t work.

Also, because the documentation for frontend-app-profile says devstack needs to be started first, I assumed the same should be true for tutor. Instead, one has to start the local MFE first with npm run start and once it’s running, follow up with tutor dev launch.

I think it would be good to update the documentation for the tutor MFE plugin, as well as each individual MFE, to include more specific details about what missteps to avoid, and how to get everything running. To that end, I’ll work on a series of pull requests to help that along. I’ll follow up here once they’re ready for review.

Thanks again for your response.

2 Likes

That’s correct, and as you mention below, likely warrants an update to the docs if it’s not clear enough.

As far as I know, tutor-mfe won’t actually use the npm run started node that’s running outside the container, but I imagine this works because as part of npm run start the MFE gets re-built. In any case, what I’m trying to say is that npm run start shouldn’t be necessary.

That would be awesome. Thanks!

Hi @arbrandes & @regis,
I’ve opened a PR against the master branch of the Tutor MFE repo.

1 Like

@rediris after mounting edx-platfrom successfully in Quince I will try the same way to mount the learner dashboard but I am facing this issue. I also checked Docker and I don’t think there is a problem there.

tutor mounts add /home/yagnesh/tvm-quin/frontend-app-learner-dashboard
tutor dev launch
[v17.0.0@tvm-quin] yagnesh@DSK-DEL-00001:~/tvm-quin$ tutor mounts list
- name: /home/yagnesh/tvm-quin/edx-platform
  build_mounts:
  - image: openedx
    context: edx-platform
  - image: openedx-dev
    context: edx-platform
  compose_mounts:
  - service: openedx
    container_path: /openedx/edx-platform

[v17.0.0@tvm-quin] yagnesh@DSK-DEL-00001:~/tvm-quin$ tutor mounts add /home/yagnesh/tvm-quin/frontend-app-learner-dashboard
Adding bind-mount: /home/yagnesh/tvm-quin/frontend-app-learner-dashboard
Configuration saved to /home/yagnesh/tvm-quin/config.yml
Environment generated in /home/yagnesh/tvm-quin/env
[v17.0.0@tvm-quin] yagnesh@DSK-DEL-00001:~/tvm-quin$ tutor dev start -d
docker compose -f /home/yagnesh/tvm-quin/env/local/docker-compose.yml -f /home/yagnesh/tvm-quin/env/local/docker-compose.prod.yml --project-name tutor_local stop
docker compose -f /home/yagnesh/tvm-quin/env/local/docker-compose.yml -f /home/yagnesh/tvm-quin/env/dev/docker-compose.yml --project-name tutor_dev up --remove-orphans -d
[+] Running 0/1
 ⠿ learner-dashboard Error                                                                                                                                                                            2.6s
Error response from daemon: pull access denied for overhangio/openedx-learner-dashboard-dev, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
Error: Command failed with status 18: docker compose -f /home/yagnesh/tvm-quin/env/local/docker-compose.yml -f /home/yagnesh/tvm-quin/env/dev/docker-compose.yml --project-name tutor_dev up --remove-orphans -d
[v17.0.0@tvm-quin] yagnesh@DSK-DEL-00001:~/tvm-quin$ tutor dev launch

it means that i cant make my own mfe? because i used
tutor mounts add path/to/my/new_mfe
tutor images build mfe
tutor dev launch
and my new mfe simple does not appear in the list


should appear http://apps.local.edly.io:2001/new-mfe

while when i use a tutor plugin that pulls the mfe directly from github, the mfe appear in the list

If I’m reading this properly, you are attempting to use port 2001 twice? Try giving your custom MFE a unique port number that doesn’t conflict with an existing MFE port.

omg you’re right! In tutor plugin i know how to set the port, with tutor mounts how can i do this?

Hi,
Are you creating a completely new, unique MFE? Or are you attempting to bind mount a customized fork of an existing MFE? If the latter, your fork absolutely must follow the naming convention of the parent repository.

Presumably you created a plugin to add your MFE, according to the Tutor MFE instructions ? If so, please paste in the code of your plugin for the community to better help you.

Also, are you using Palm or Quince?

And the port specified in the tutor plugin should be respected by the bind mount.

Im creating a completely new MFE, the code are in this repo

using this snnipet it works
from tutormfe.hooks import MFE_APPS

@MFE_APPS.add()
def _add_my_mfe(mfes):
    mfes["mymfe"] = {
        "repository": "https://github.com/myorg/mymfe.git",
        "port": 2001,
        "version": "me/my-custom-branch-or-tag", # optional, will default to the Open edX current tag.
    }
    return mfes

but with bind mount, no.

Im using Quince

Try this in your plugin:

from tutormfe.hooks import MFE_APPS

@MFE_APPS.add()

def _add_my_mfe(mfes):
    mfes["attendence"] = {
        "repository": "https://github.com/rsuigh/frontend-app-attendence.git",
        "version": "main",
        "refs": "https://api.github.com/repos/rsuigh/frontend-app-attendence/git/refs/heads",
        "port": 2020, 
        "name": "attendence",
    }
    return mfes

Be sure your plugin is activated and do a tutor config save

It isn’t clear in the instructions, but running tutor images build mfe only builds the MFE image for tutor local mode, and the image contains all of the MFEs combined. For tutor dev mode, each of the MFEs have their own image built. And if you’re bind-mounting an MFE, there is no need to build the image manually.

For future reference, if you’re running tutor in dev mode (and not bind-mounting), you’d build your MFE by appending -dev to the image name, like this: tutor images build attendence-dev or tutor images build authn-dev, etc.

Check your config.yml file. The MOUNTS should looks like:

MOUNTS:
- path/to/frontend-app-attendence

Note: the actual directory matches the full name of the repo.

Before you run the tutor dev launch, be sure to run npm install in the root of your new MFE.

Let me know if any of that clears it up.

When I do it via tutor plugin I can make it work.
The process I perform is as follows:

  1. Push my new MFE to repo
  2. edit the following snnipet
  1. tutor plugins enable attendence
  2. tutor config save
  3. tutor images build mfe
  4. tutor dev launch
    THIS WORKS! But slow development method

What not work:

tutor mounts add path/to/my/new_MFE #already npm install in folder
tutor config save
tutor images build mfe
tutor dev launch

i dont know why tutor mounts method works only with MFEs in this repo

Maybe it’s a caching issue?

Try tutor images build all --no-cache --no-registry-cache

Maybe, the docs says only this:

This works for custom MFEs, as well. For example, if you added your own MFE named frontend-app-myapp, then you can bind-mount it like so:

tutor mounts add /path/to/frontend-app-myapp

Similarly, in production, the “mfe” Docker image will be rebuilt automatically during tutor local launch.

ill try --no-cacho --no-registry-cache

he built all these containers, but not my MFE

=> [profile-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-profile.git  0.1s
 => [ora-grading-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-ora-gra  0.3s
 => [learning-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-learning.g  0.5s
 => [discussions-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-discuss  0.3s
 => [communications-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-comm  0.2s
 => [course-authoring-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-co  0.6s
 => [learner-dashboard-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-l  0.5s
 => [i18n 1/3] COPY ./i18n /openedx/i18n                                                           0.0s
 => [authn-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-authn.git#ope  0.2s
 => [account-git 1/1] ADD --keep-git-dir=true https://github.com/openedx/frontend-app-account.git  0.2s
 => [gradebook-common 1/5] COPY --from=gradebook-src /package.json /openedx/app/package.json       0.0s
 => [gradebook-i18n 1/5] COPY --from=gradebook-src / /openedx/app                                  0.2s
 => [profile-src 1/1] COPY --from=profile-git /openedx/app /                                       0.0s
 => [ora-grading-src 1/1] COPY --from=ora-grading-git /openedx/app /                               0.1s
 => [learning-src 1/1] COPY --from=learning-git /openedx/app /                                     0.1s
 => [discussions-src 1/1] COPY --from=discussions-git /openedx/app /                               0.1s
 => [communications-src 1/1] COPY --from=communications-git /openedx/app /                         0.0s
 => [i18n 2/3] RUN chmod a+x /openedx/i18n/*.js                                                    0.1s
 => [learner-dashboard-src 1/1] COPY --from=learner-dashboard-git /openedx/app /                   0.1s
 => [course-authoring-src 1/1] COPY --from=course-authoring-git /openedx/app /                     0.1s
 => [gradebook-common 2/5] COPY --from=gradebook-src /package-lock.json /openedx/app/package-lock  0.0s
 => [account-src 1/1] COPY --from=account-git /openedx/app /                                       0.1s
 => [authn-src 1/1] COPY --from=authn-git /openedx/app /                                           0.1s
 => [profile-common 1/5] COPY --from=profile-src /package.json /openedx/app/package.json           0.1s
 => [profile-i18n 1/5] COPY --from=profile-src / /openedx/app                                      0.0s
 => [ora-grading-common 1/5] COPY --from=ora-grading-src /package.json /openedx/app/package.json   0.0s
 => [ora-grading-i18n 1/5] COPY --from=ora-grading-src / /openedx/app                              0.1s
 => [discussions-i18n 1/5] COPY --from=discussions-src / /openedx/app                              0.1s
 => [discussions-common 1/5] COPY --from=discussions-src /package.json /openedx/app/package.json   0.0s
 => [communications-i18n 1/5] COPY --from=communications-src / /openedx/app                        0.0s
 => [communications-common 1/6] COPY --from=communications-src /package.json /openedx/app/package  0.0s
 => [learning-i18n 1/5] COPY --from=learning-src / /openedx/app                                    0.1s
 => [learning-common 1/5] COPY --from=learning-src /package.json /openedx/app/package.json         0.0s
 => [i18n 3/3] RUN echo "copying i18n data"   && mkdir -p /openedx/i18n/authn   && mkdir -p /open  0.1s
 => [learner-dashboard-i18n 1/5] COPY --from=learner-dashboard-src / /openedx/app                  0.1s
 => [learner-dashboard-common 1/5] COPY --from=learner-dashboard-src /package.json /openedx/app/p  0.0s
 => [gradebook-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-in  280.7s
 => [account-i18n 1/5] COPY --from=account-src / /openedx/app                                      0.1s
 => [account-common 1/5] COPY --from=account-src /package.json /openedx/app/package.json           0.0s
 => [course-authoring-common 1/5] COPY --from=course-authoring-src /package.json /openedx/app/pac  0.0s
 => [course-authoring-i18n 1/5] COPY --from=course-authoring-src / /openedx/app                    0.1s
 => [profile-common 2/5] COPY --from=profile-src /package-lock.json /openedx/app/package-lock.jso  0.0s
 => [authn-common 1/5] COPY --from=authn-src /package.json /openedx/app/package.json               0.0s
 => [authn-i18n 1/5] COPY --from=authn-src / /openedx/app                                          0.1s
 => [ora-grading-common 2/5] COPY --from=ora-grading-src /package-lock.json /openedx/app/package-  0.1s
 => [discussions-common 2/5] COPY --from=discussions-src /package-lock.json /openedx/app/package-  0.1s
 => [communications-common 2/6] COPY --from=communications-src /package-lock.json /openedx/app/pa  0.0s
 => [learning-common 2/5] COPY --from=learning-src /package-lock.json /openedx/app/package-lock.j  0.0s
 => [profile-i18n 2/5] COPY --from=i18n /openedx/i18n/profile /openedx/i18n/profile                0.0s
 => [discussions-i18n 2/5] COPY --from=i18n /openedx/i18n/discussions /openedx/i18n/discussions    0.0s
 => [gradebook-i18n 2/5] COPY --from=i18n /openedx/i18n/gradebook /openedx/i18n/gradebook          0.0s
 => [learning-i18n 2/5] COPY --from=i18n /openedx/i18n/learning /openedx/i18n/learning             0.0s
 => [ora-grading-i18n 2/5] COPY --from=i18n /openedx/i18n/ora-grading /openedx/i18n/ora-grading    0.0s
 => [communications-i18n 2/5] COPY --from=i18n /openedx/i18n/communications /openedx/i18n/communi  0.0s
 => [learner-dashboard-common 2/5] COPY --from=learner-dashboard-src /package-lock.json /openedx/  0.0s
 => [learner-dashboard-i18n 2/5] COPY --from=i18n /openedx/i18n/learner-dashboard /openedx/i18n/l  0.0s
 => [account-i18n 2/5] COPY --from=i18n /openedx/i18n/account /openedx/i18n/account                0.0s
 => [account-common 2/5] COPY --from=account-src /package-lock.json /openedx/app/package-lock.jso  0.0s
 => [course-authoring-common 2/5] COPY --from=course-authoring-src /package-lock.json /openedx/ap  0.0s
 => [course-authoring-i18n 2/5] COPY --from=i18n /openedx/i18n/course-authoring /openedx/i18n/cou  0.0s
 => [profile-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-inst  136.2s
 => [authn-common 2/5] COPY --from=authn-src /package-lock.json /openedx/app/package-lock.json     0.0s
 => [authn-i18n 2/5] COPY --from=i18n /openedx/i18n/authn /openedx/i18n/authn                      0.0s
 => [ora-grading-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-i  38.8s
 => [discussions-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-i  39.1s
 => [communications-common 3/6] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clea  59.6s
 => [learning-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-ins  131.2s
 => [profile-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.js    0.1s
 => [discussions-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.  0.0s
 => [gradebook-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.js  0.0s
 => [learning-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.js   0.0s
 => [ora-grading-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.  0.0s
 => [communications-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-mer  0.0s
 => [learner-dashboard-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm c  38.7s
 => [learner-dashboard-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-  0.1s
 => [account-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.js    0.0s
 => [account-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-insta  41.6s
 => [course-authoring-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm cl  89.5s
 => [course-authoring-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-m  0.1s
 => [profile-common 4/5] COPY --from=profile-src / /openedx/app                                    4.9s
 => [authn-common 3/5] RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-install  42.0s
 => [authn-i18n 3/5] COPY --from=i18n /openedx/i18n/i18n-merge.js /openedx/i18n/i18n-merge.js      0.1s
 => [ora-grading-common 4/5] COPY --from=ora-grading-src / /openedx/app                            4.7s
 => [discussions-common 4/5] COPY --from=discussions-src / /openedx/app                            2.7s
 => [communications-common 4/6] COPY --from=communications-src / /openedx/app                      4.5s
 => [gradebook-common 4/5] COPY --from=gradebook-src / /openedx/app                                4.6s
 => [profile-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing mes  0.1s
 => [discussions-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing  0.1s
 => [gradebook-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing m  0.1s
 => [learning-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing me  0.1s
 => [ora-grading-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing  0.1s
 => [communications-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "miss  2.3s
 => [learner-dashboard-common 4/5] COPY --from=learner-dashboard-src / /openedx/app                2.5s
 => [learner-dashboard-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "m  0.1s
 => [account-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing mes  0.1s
 => [account-common 4/5] COPY --from=account-src / /openedx/app                                    6.0s
 => [learning-common 4/5] COPY --from=learning-src / /openedx/app                                  6.1s
 => [course-authoring-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "mi  0.1s
 => [course-authoring-common 4/5] COPY --from=course-authoring-src / /openedx/app                  2.7s
 => [authn-i18n 4/5] RUN stat /openedx/app/src/i18n/messages 2> /dev/null || (echo "missing messa  2.4s
 => [authn-common 4/5] COPY --from=authn-src / /openedx/app                                        1.0s
 => [profile-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /openedx/i1  0.8s
 => [discussions-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /opened  0.1s
 => [gradebook-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /openedx/  0.1s
 => [learning-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /openedx/i  0.1s
 => [ora-grading-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /opened  0.1s
 => [communications-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /ope  0.1s
 => [learner-dashboard-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /  0.1s
 => [account-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /openedx/i1  0.1s
 => [course-authoring-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /o  0.1s
 => [authn-i18n 5/5] RUN /openedx/i18n/i18n-merge.js /openedx/app/src/i18n/messages /openedx/i18n  0.1s
 => [profile-common 5/5] COPY --from=profile-i18n /openedx/app/src/i18n/messages /openedx/app/src  0.0s
 => [discussions-common 5/5] COPY --from=discussions-i18n /openedx/app/src/i18n/messages /openedx  0.0s
 => [gradebook-common 5/5] COPY --from=gradebook-i18n /openedx/app/src/i18n/messages /openedx/app  0.0s
 => [ora-grading-common 5/5] COPY --from=ora-grading-i18n /openedx/app/src/i18n/messages /openedx  0.1s
 => [learning-common 5/5] COPY --from=learning-i18n /openedx/app/src/i18n/messages /openedx/app/s  0.1s
 => [learner-dashboard-common 5/5] COPY --from=learner-dashboard-i18n /openedx/app/src/i18n/messa  0.0s
 => [communications-common 5/6] COPY --from=communications-i18n /openedx/app/src/i18n/messages /o  0.0s
 => [account-common 5/5] COPY --from=account-i18n /openedx/app/src/i18n/messages /openedx/app/src  0.0s
 => [course-authoring-common 5/5] COPY --from=course-authoring-i18n /openedx/app/src/i18n/message  0.1s
 => [profile-prod 1/1] RUN npm run build                                                          99.2s
 => [discussions-prod 1/1] RUN npm run build                                                     193.2s
 => [gradebook-prod 1/1] RUN npm run build                                                        55.2s
 => [authn-common 5/5] COPY --from=authn-i18n /openedx/app/src/i18n/messages /openedx/app/src/i18  0.2s
 => [ora-grading-prod 1/1] RUN npm run build                                                     153.3s
 => [learning-prod 1/1] RUN npm run build                                                        136.5s
 => [learner-dashboard-prod 1/1] RUN npm run build                                               139.0s
 => [communications-common 6/6] RUN make OPENEDX_ATLAS_PULL=true pull_translations                 2.0s
 => [account-prod 1/1] RUN npm run build                                                         108.4s
 => [course-authoring-prod 1/1] RUN npm run build                                                202.3s
 => [authn-prod 1/1] RUN npm run build                                                            60.1s
 => [communications-prod 1/1] RUN npm run build

What happens when you do a tutor images build frontend-app-attendence-dev --no-cache --no-registry-cache?


As an aside, it might be worth doing a search for attendence inside your local tutor/env directory. Your MFE should be referenced in the following env files:

  • /env/apps/openedx/settings/lms/development.py
  • /env/dev/docker-compose.yml
  • /env/plugins/mfe/apps/mfe/Caddyfile
  • /env/plugins/mfe/build/mfe/Dockerfile

In my case, I have a custom course-about MFE working as expected as a bind-mount, and the MFE is referenced in each of the files I listed above.