Serverless Compose Path Resolution and Build Issues in Monorepo Setup

Environment

* **Serverless Framework Version:** v4.17.1

* **Tool:** Serverless Compose

* **Operating System:** Windows

* **Node.js Version:** v20.9.0

* **Project Structure:** Monorepo with `serverless-compose.yml` at project root

/

├── serverless-compose.yml

├── serverless_custom.yml

├── package.json

├── src/

│ ├── chat/

│ ├── user/

│ └── … (shared source code for all Lambdas)

└── services/

├── compose1/ 

│   └── serverless.yml 

├── compose2/ 

│   └── serverless.yml 

└── compose3/ 

    └── serverless.yml 

Goal

To deploy **three separate services** (`compose1`, `compose2`, `compose3`) using a **single** `serverless deploy` command, where all services share a **common `src/` directory** located at the project root.

* Each service’s `serverless.yml` uses function definitions split across multiple YAML files within the `src/` folder via `${file(../../src/…)}`

* The `src/` folder contains shared business logic and Lambda handlers.


Problem

When executing `serverless package` or `serverless deploy` from the **project root**, the process fails for all services due to **path resolution issues**. Specifically:

* Each individual service (`compose1`, `compose2`, etc.) **cannot resolve the shared root-level `src/` directory** correctly during the **build and packaging** phase.

-–

Specific Errors Encountered

Path Resolution Failure

Cannot resolve '${file(../../src/...)}' variable. No value is available for this variable... 

Compilation Failure (Most Common)

Compilation failed for function alias <function-name>.  
Please ensure you have an index file with ext .ts or .js,  
or have a path listed as main key in package.json 

serverless-esbuild Plugin Error

Invalid option in context() call: "srcDir"

Built-in esbuild Failure

ENOENT: no such file or directory, open 'F:\...\services\compose1\.serverless\build\package.json'


### **Troubleshooting Attempts**

We have explored multiple workarounds, but none fully resolved the issue:

1. Used both:

* `serverless-esbuild` plugin

* Built-in `build.esbuild` option

2. Explicitly set `srcDir` in `esbuild` config — results in plugin error.

3. Prepend the correct relative path (src/...) to all handler

4. Added dummy `package.json` files to each `services/*` directory.

5. Validated all `${file(…)}` paths relative to each `serverless.yml`.


Conclusion
It seems that Serverless Compose has difficulty handling a **shared `src/` folder** when each service’s build context is isolated. We’re looking for a **recommended, idiomatic approach** to:

* Use a **single shared codebase (`src/`)** for multiple services

* Enable **esbuild bundling** (plugin or built-in)

* Avoid duplication of code or `package.json` across services

* Maintain clean separation of service definitions in `services/`


Please advise on the **best practice** for making this **monorepo setup** work reliably with:

* **Serverless Compose**

* **Esbuild (plugin or built-in)**

Thanks for submitting the query about Serverless Compose. To better assist we would likely need to see the current configurations for serverless-compose.yml. However, if you feel like sharing that information here is too public, we can continue the conversation via support@serverless.com. Once we are able to work through your use case you would then be more than welcome to post the solution here anyone else looking (or give us permission to).

# serverless-compose.yml
services:
  compose1:
    path: services/compose1
  compose2:
    path: services/compose2
  compose3:
    path: services/compose3

services/compose1/serverless.yml

service: compose1
frameworkVersion: “4.17.1”

useDotenv: true

build:

esbuild:

bundle: true

minify: true

sourcemap:

  type: linked

  setNodeOptions: true

keepNames: true

buildConcurrency: 1

zipConcurrency: 3

watch:

  pattern: \[ "src/\*\*/\*.(js|ts)" \]

srcDir: ../../

package:

individually: true

excludeDevDependencies: true

include:

- templates/\*\*

exclude:

- .git/\*\*

- .gitignore

- README.md

- package.json

- package-lock.json

- node_modules/\*\*

- test/\*\*

- .dynamodb/\*\*

- backups/\*\*

- resources/\*\*

plugins:

  • serverless-offline

  • serverless-prune-plugin

  • serverless-step-functions

custom: ${file(../../serverless_custom.yml)}

provider:

name: aws

stages:

default:

params: ${ssm:/aws/reference/secretsmanager/appName/${sls:stage}}

functions:

  • ${file(../../src/user/setting/routes.yml)}

  • ${file(../../src/subscription/config.yml)}

  • ${file(../../src/subscription/routes.yml)}

  • ${file(../../src/shared/system/config.yml)}

  • ${file(../../src/shared/crm/config.yml)}

  • ${file(../../src/shared/content/routes.yml)}

  • ${file(../../src/shared/image/routes.yml)}

  • ${file(../../src/shared/event/routes.yml)}

  • ${file(../../src/shared/growthbook/routes.yaml)}

Hey @garethmcc , I’ve shared the serverless-compose current config. As well as one of the serverless.yml files in compose1, which is siilar to those in compose2 and compose3

Thank you for prompting about the secret key, I will make sure to share any such details privately.

As an alternative, you can avoid this issue letting npm (or similar) handle the task of sharing code among different services. Each compose has its own package.json and you can use node workspaces to share common code among them.