DEV Community

Julia for Light Cloud

Posted on Originally published at blog.light-cloud.com on

Deploy From a Monorepo: One Repository, Several Apps

Deploy From a Monorepo: One Repository, Several Apps

To deploy a monorepo on Light Cloud, create one app per folder from the same GitHub repository and set each app's Root directory to its folder. Light Cloud detects the framework inside that folder, builds only that folder, and from then on a push redeploys only the apps whose folder changed. Here that is a React site in apps/web and an Express API in apps/api, deployed as two apps from one repository.

What you will build

Two apps from one repository, grouped in a folder in the console:

  • apps/api, an Express service that answers JSON, deployed as a container,
  • apps/web, a React page built with Vite, served as a static site, which calls the API.

The deployed web app: the label apps/web, the heading One repository, many apps, the line This page is the web app, Version 2, and the JSON the api app answered with service api, version 2 and a time

Source code: github.com/light-cloud-com/tutorial-monorepo.

Before you start

Step 1: Get the example repository

Open the example repository on GitHub, click Fork , then Create fork. Clone your fork, with your GitHub username in place of YOUR-USERNAME:

$ git clone https://github.com/YOUR-USERNAME/tutorial-monorepo.git
$ cd tutorial-monorepo


PS> git clone https://github.com/YOUR-USERNAME/tutorial-monorepo.git
PS> cd tutorial-monorepo

Enter fullscreen mode Exit fullscreen mode

The layout is one folder per app, and nothing at the root that would build:

apps/
  api/ Express service: server.js, package.json, package-lock.json
  web/ React site built with Vite: index.html, src/, package.json, package-lock.json
README.md

Enter fullscreen mode Exit fullscreen mode

Each folder has its own package.json and lock file. That matters: Light Cloud builds an app from its Root directory only, and files outside it are neither built with the app nor watched for changes.

Step 2: Make a folder for the project

A folder keeps the two apps together in the console. Click New... at the top of the sidebar, then New Folder , name it monorepo and click Create folder :

The Create a folder dialog with the name monorepo, Parent folder None (root level), and the Create folder button highlighted

The folder appears in the sidebar. Its + button adds things to it; that is where the next two steps start:

The sidebar folder monorepo with its plus menu open and Deploy from GitHub highlighted

Step 3: Deploy the API from apps/api

  1. Click the folder's + , then Deploy from GitHub. In Search your repositories... , type tutorial-mono and pick tutorial-monorepo.

Light Cloud looks at the repository root, finds no project there, and says so:

The yellow banner We're not sure what this is, Deploying this as-is is a guess, check the settings below, with the folder button next to Root directory highlighted

  1. Click the folder button next to Root directory. It lists the repository's folders; expand apps with + and click api :

The folder browser under Root directory, showing the repository root, apps expanded, and api highlighted next to web

Detection runs again, inside apps/api this time: Express / Backend , based on package.json and package-lock.json, Runs on a server. The Port under Advanced is already 8080, which is what server.js listens on through process.env.PORT.

  1. The Name still reads tutorial-monorepo, the repository name, because the folder does not rename the app. Type monorepo-api; the name becomes part of the address. Then click Deploy :

The deploy form with Express Backend detected, the Name monorepo-api and the Root directory apps/api highlighted, Framework Express, Size Micro, Region Belgium, and the Deploy button highlighted

A container build takes longer than a static site: the build finished in 1 minute 5 seconds, and the API answered at https://main-monorepo-api-examples.light-cloud.io two minutes after I clicked Deploy (examples is my workspace; yours is in the address instead). Open yours in the browser: it answers with {"service":"api","version":1,"time":"..."}. Copy the address; the web app needs it.

Step 4: Deploy the web app from apps/web

  1. Click the folder's + again, then Deploy from GitHub , and pick tutorial-monorepo once more. Same repository, second app.

  2. This time type apps/web into Root directory and press Enter. Detection finds React / Frontend and fills in npm ci, npm run build and the output folder dist.

  3. Type monorepo-web as the Name.

  4. Open Advanced - build settings, environment variables, domain, scaling , click Add variable and add VITE_API_URL with your API address from Step 3 as the value. Vite reads VITE_ variables when it builds the site, so the page knows where its API lives. Then click Deploy :

The deploy form for the web app with Root directory apps/web highlighted, Framework React detected, Install command npm ci, Output directory dist, Build command npm run build, the environment variable VITE_API_URL highlighted, and the Deploy button highlighted

The static build took 19 seconds, and the page was live 60 seconds after the click, at https://main-monorepo-web-examples.light-cloud.io. It calls the API and shows the answer:

The web app page: apps/web, One repository, two apps, This page is the web app, Version 1, and the JSON the api app answered with service api, version 1 and a time

The folder page shows both apps from the one repository:

The monorepo folder with two cards: monorepo-api, Node.js, 1 environment, Deployed, and monorepo-web, React, 1 environment, Deployed

Step 5: Push a change to one app

Now the part that makes a monorepo pleasant: a push redeploys only the app whose folder it touched. Change the version line in the web app:

<p>This page is the web app. Version 2.</p>

Enter fullscreen mode Exit fullscreen mode

Commit and push:

$ git commit -am "web: version 2"
$ git push
To https://github.com/YOUR-USERNAME/tutorial-monorepo.git
   da9cef2..a99b162 main -> main


PS> git commit -am "web: version 2"
PS> git push
To https://github.com/YOUR-USERNAME/tutorial-monorepo.git
   da9cef2..a99b162 main -> main

Enter fullscreen mode Exit fullscreen mode

Your output shows your own commit ids. Only the web app redeployed: its Deployments tab has a new entry by GitHub Auto-Deploy , 14 seconds in my run:

Deployment History of monorepo-web: the new deployment web: version 2 by GitHub Auto-Deploy highlighted, then the first deployment started by hand

Now change the API's version in server.js from const VERSION = 1; to const VERSION = 2;, commit with the message api: version 2 and push the same way. This time only the API rebuilt, and the web app's history did not change. The API's own history shows its two deployments and nothing from the web commit:

Deployment History of monorepo-api: the new deployment api: version 2 by GitHub Auto-Deploy highlighted, then the first deployment started by hand

Refresh the web page: it shows "version": 2 from the API, with no new build of its own.

One more push to prove the rule: add a line to README.md, commit and push. The README is outside both folders, so nothing deployed at all; GitHub shows no check on that commit. Light Cloud compares the files in the pushed commits with each app's Root directory and skips the apps that are not affected.

Step 6: Preview a branch

Branches behave a little differently from pushes to main. Create one and change the heading in apps/web/src/App.jsx from One repository, two apps to One repository, many apps:

$ git switch -c new-heading
$ git commit -am "web: new heading"
$ git push -u origin new-heading
remote: Create a pull request for 'new-heading' on GitHub by visiting:
remote: https://github.com/YOUR-USERNAME/tutorial-monorepo/pull/new/new-heading
To https://github.com/YOUR-USERNAME/tutorial-monorepo.git
 * [new branch] new-heading -> new-heading
branch 'new-heading' set up to track 'origin/new-heading'.


PS> git switch -c new-heading
PS> git commit -am "web: new heading"
PS> git push -u origin new-heading
remote: Create a pull request for 'new-heading' on GitHub by visiting:
remote: https://github.com/YOUR-USERNAME/tutorial-monorepo/pull/new/new-heading
To https://github.com/YOUR-USERNAME/tutorial-monorepo.git
 * [new branch] new-heading -> new-heading
branch 'new-heading' set up to track 'origin/new-heading'.

Enter fullscreen mode Exit fullscreen mode

A new branch gets a preview environment for every app on the repository, not only for the app whose folder changed. Both apps now list New Heading next to Production ; the web preview was answering 25 seconds after the push, the API preview after a minute and a half, because its container was built again:

The Environments tab of monorepo-web listing Production on main and New Heading on the branch new-heading, highlighted

Open the link GitHub printed and click Create pull request. Light Cloud comments once per app, each with its own preview address:

Two comments from the light-cloud-ci bot on the pull request: Light Cloud, monorepo-api / New Heading, Ready, with its preview address, and Light Cloud, monorepo-web / New Heading, Ready, with its preview address, and below them This branch was successfully deployed, 2 active deployments

The preview environments start with each app's own variables. So the web preview still has VITE_API_URL pointing at the production API, and that is what it shows:

The web preview page with the new heading One repository, many apps and the JSON from the production api, version 2

If a preview must talk to its own API preview instead, open the web preview environment's Settings and give VITE_API_URL the API preview's address; the site rebuilds with it.

Merge the branch and delete it:

$ git switch main
$ git merge new-heading
Updating d82f329..8c35173
Fast-forward
 apps/web/src/App.jsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
$ git push origin --delete new-heading


PS> git switch main
PS> git merge new-heading
Updating d82f329..8c35173
Fast-forward
 apps/web/src/App.jsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
PS> git push
PS> git push origin --delete new-heading

Enter fullscreen mode Exit fullscreen mode

The merge is a push to main that touches only apps/web, so only the web app redeployed (17 seconds). Deleting the branch removed both preview environments: the API preview was gone 6 seconds later, the web preview after a minute, and both comments on the pull request changed to Preview removed.

Change the folder later

An app's folder is not fixed at creation. Open the app (not an environment), click Settings , and change Root Directory. The new folder is used from the next deployment on:

The Root Directory card in the app settings: The folder of the repository this app builds from, Leave empty for the repository root, Takes effect on the next deployment, with the value apps/web highlighted

Light Cloud names things after the app once a repository has more than one: the GitHub checks read Light Cloud - monorepo-web / Production and Light Cloud - monorepo-api / Production , and so do the pull request comments, so you can always tell which app a result belongs to.

Troubleshooting

We're not sure what this is

The repository root has no project files Light Cloud recognises, which is normal for a monorepo. Set Root directory to the app's folder; detection runs again inside it.

Could not reach the api: VITE_API_URL is not set

The web app was built without the variable. Add VITE_API_URL in the environment's Settings and let it redeploy: a static site reads variables when it builds, not when a visitor opens it.

A push redeployed nothing

Only apps whose Root directory contains a changed file redeploy. Check which folder the commit touched; a change to the repository's README or to another app's folder does not deploy this one.

Top comments (0)