Monorepo project with pnpm workspaces

Hey,

I’m trying to deploy a project containing a backend project in PHP and multiple frontend projects built with Remix. The root of the project contains the workspace configuration for pnpm, the project is structured as follows:

  • root
    • backend (Symfony)
    • frontend
      • apps
        • app1 (Remix)
          • package.json
        • app2 (Remix)
          • package.json
        • app3 (Remix)
          • package.json
  • package.json
  • pnpm-lock.yaml
  • pnpm-workspace.yaml

The problem I’m having is that when I attempt to build “app1” for example I’m getting an error when trying to install packages, I’m guessing this is related to it being a workspace project but only the folder of “app1” is included in the build process so it does not have access to the lock-file or workspace configured in a parent directory.

I’m not really sure how I wold go about solving this with platform.sh since the documentation does not go into this and googling does not give any results.

The error I’m getting is the following:

ERROR  Headless installation requires a pnpm-lock.yaml file

You may try using the applications.yaml to define each app. Then, in your build hook run pnpm install from the directory that contains the pnpm-lock.yaml.

Something like:

# .platform/applications.yaml
-   name: app1
    type: nodejs:14
    source:
        root: frontend/apps/app1
    hooks:
        build: |
           cd .. && pnpm install
           # Or cd .. && pnpm install -C frontend/apps/app1
    web:
        commands:
            start: (your start command)

Thanks, looks like that solved it, weird that I can’t install from the sub-folder tough when the same thing works fine locally.

Now I’m getting a different error message tough that I’m not sure how to solve, looks like pnpm stores files in a temporary directory that should be the temporary directory of the current user according to issues I found when googling, my understanding is that the filesystem should be writable during the build hook so not sire why I’m getting the error or how to fix it.

`ERROR  EROFS: read-only file system, open '/_tmp_109_d69456fc24e31ca90a51b7544a406409

Ok, so I’ve been playing around with this some more now to try to get it to work. I’ve updated to the correct version of pnpm, previously it as at version 6 while the project uses version 7.

But I still can’t get the build to work so i started doing some “ls -al” commands during the build step to list the contents of the directory that the build starts of in and the parent directory and it looks like it only contains the folder of the application so it only includes the “frontend/apps/app1” directory as a directory named “/app” so trying to cd to the parent directory or setting the base directory for the pnpm install command does not work since there is nothing outside of the “/app” directory related to the project.

I’m not sure how to continue with this since I want to install packages for a specific app and have it be able to access dependencies in the full workspace but then only end up with the final build containing only the specific app so that the container is not bloated with the full project.

That makes sense. Each app only has its root directory exposed. My original suggestion would not have worked under any scenario.

The alternative, I think, is to configure it as a single app. You’d:

  • Add your .platform.app.yaml to the root directory and configure it as a PHP app for your Symfony backend.
  • Add Node.js, PNPM, Yarn/Node - whatever you need for the build to the project
  • Since the Remix apps are probably static apps, you should be able to use your web.locations to route to those paths and treat them as static locations.

We’ll look into how we can better support workspaces out-of-the-box. In the meantime, @chad-carlson may have something better to offer than what I suggested :crossed_fingers:

@normysan If it’s necessary that all 3 frontend apps need access to the same lock file, and that that lock file has to be in root, I would try to define all of the apps in a shared applications.yaml file and leverage source.root.

For Symfony, source.root can be backend, but for the three frontend apps, I would try defining source.root: . for each of them, and then redefining the builds to their correct subdirectories.

As for the write access error, everything should in fact be writable at the build stage. There will be certain directories off of / that remain read only for security reasons, so my guess is you’re running into that with this error. I would start by seeing if you can explicitly define that temp directory with an environment variable to something like app/tmp.

1 Like

Thanks for the suggestions, Remix is actually not static but runs as a node server that does SSR with React. The apps need a shared lock file and also need to be a workspace since there is configuration and components and more being shared between the apps.

I will try the suggestion of using “.” as the “source.root” for the frontend apps. How would I then make sure that the frontend apps do not contain the full backend project including the other apps after build and how will platform.sh know to not rebuild the apps when something changes in the backend folder for example since I’m guessing it decides this based on the “source.root” setting?

With the configuration I’m suggesting, each of the frontend application containers will contain anything committed to backend as a part of their filesystems. That said, builds are based on the tree in Git, and so both by placing everything Symfony in backend and defining source.root as I’ve suggested, builds will still occur in isolation from each other.

  • backend will contain vendor, but apps 1-3 will not.
  • A change to a file in backend will cause a rebuild for the backend app, but no other app has to be rebuilt.
  • same goes for the frontend apps. They’ll each have identical copies of the root directory in their filesystems, but will only contain a node_modules for app1 in the app1 container.

Then this should give each frontend app access to the same lock file as you need. The only thing to be careful of here is how this isolation, and how Platform.sh isolates build processes in general when you’re trying to share information between containers.

Your containers exist in isolation - that is, if you don’t define a route to an app, nothing can reach it except for an SSH session. This is the case at runtime, but also during the build hook, when no container can access any other even when a relationship or route is defined.

I bring this up for two reasons:

  • you may find yourself trying to pull data from Symfony into app1 during it’s build hook. This won’t always work, as the Symfony app for the current environment hasn’t yet deployed. In most cases, this is only relevant for the first branching commit for new environments. You can see an example of delaying a frontend Node.js app’s build to get around this in the Next.js-Drupal template.
  • if there are files that need to be shared between apps1-3 (here I mean files that have been written at runtime for example) just using the definition source.root: . will not give you access to them (since we’re talking about file system copies here). If you do need something like this, check out the Network Storage service type.

You might not run into either of these cases. I’m only mentioning because I’ve gotten caught on both deploying multi-app environments on Platform.sh.