Upsun User Documentation

Choose a project structure

Sign up

Get your free trial by clicking the link below.

Get your Upsun free trial

How you structure a project with multiple apps depends on how your code is organized and what you want to accomplish. For example, there are various ways you could set up the following multiple apps:

Here are some example use cases and potential ways to organize the project:

Use case Structure
Separate basic apps that are worked on together. Unified app configuration
One app depends on code from another app. Nested directories
You want to keep configuration separate from code, such as through Git submodules. Configuration separate from code
You want multiple apps from the same source code. Unified app configuration
You want to control all apps in a single location. Unified app configuration

Unified app configuration Anchor to this heading

You can configure all your apps from a single file. To do so, create a .upsun/config.yaml and define each app as a key.

For example, if you have an API Platform backend with a Symfony API, a Mercure Rocks server, and a Gatsby frontend, you can organize your repository like this:

โ”œโ”€โ”€ .upsun
โ”‚   โ”œโ”€โ”€ config.yaml   <- Unified configuration
โ”œโ”€โ”€ admin
โ”‚   โ””โ”€โ”€ ...                 <- API Platform Admin app code
โ”œโ”€โ”€ api-app
โ”‚   โ””โ”€โ”€ ...                 <- Bigfoot app code
โ”œโ”€โ”€ gatsby
โ”‚   โ””โ”€โ”€ ...                 <- Gatsby app code
โ””โ”€โ”€ mercure
    โ””โ”€โ”€ ...                 <- Mercure Rocks app code

The api app is built from the api-app directory.
The admin app is built from the admin directory.
The gatsby app is built from the gatsby directory.
The mercure app is built from the mercure directory.
They all have different configurations for how they serve the files. For more details, see the complete example file.

To allow your apps to communicate with each other, define relationships.

Note that with this setup, when you amend the code of one of your apps, the build image for your other apps can still be reused.

Once your repository is organized, you can use a configuration similar to the following:

    type: php:8.2

      database: "database:postgresql"

      "/var/cache": "shared:files/cache"
      "/var/log": "shared:files/log"
      "/var/sessions": "shared:files/sessions"

          root: "public"
          passthru: '/index.php'
            - index.php
            Access-Control-Allow-Origin: "*"

      build: |
        set -x -e
        curl -s | bash

      deploy: |
        set -x -e

      root: api-app

    type: nodejs:16

      '/.tmp_platformsh': 'shared:files/tmp_platformsh'
      '/build': 'shared:files/build'
      '/.cache': 'shared:files/.cache'
      '/node_modules/.cache': 'shared:files/node_modules/.cache'

          root: "build"
          passthru: "/admin/index.html"
            - "index.html"
            Access-Control-Allow-Origin: "*"

      build: |
        set -eu
        corepack yarn install --immutable --force        
      post_deploy: |
        corepack yarn run build        
      root: admin

    type: 'nodejs:18'

      '/.cache': { source: tmp, source_path: cache }
      '/.config': { source: storage, source_path: config }
      '/public': { source: storage, source_path: public }

          root: 'public'
          index: [ 'index.html' ]
          scripts: false
          allow: true

      build: |
        set -e
        yarn --frozen-lockfile        
      post_deploy: |
        yarn build --prefix-paths        
      root: gatsby

    type: golang:1.18

      'database': { source: storage, source_path: 'database' }
      '/.local': { source: storage, source_path: '.local' }
      '/.config': { source: storage, source_path: '.config' }

        start: ./mercure run --config Caddyfile.platform_sh

          passthru: true
          scripts: false
            enabled: false
            Access-Control-Allow-Origin: "*"

      build: |
        # Install Mercure using cache
        if [ ! -f "$PLATFORM_CACHE_DIR/$FILE" ]; then
          wget -O "$PLATFORM_CACHE_DIR/$FILE" $URL
          echo "Found $FILE in cache, using cache"
        tar xvzf $PLATFORM_CACHE_DIR/$FILE        

      root: mercure/.config

Nested directories Anchor to this heading

When code bases are separate, changes to one app don’t necessarily mean that the other apps in the project get rebuilt. You might have a situation where app main depends on app languagetool, but languagetool doesn’t depend on main.

In such cases, you can nest the dependency so the parent (main) gets rebuilt on changes to it or its children, but the child (languagetool) is only rebuilt on changes to itself.

For example, you might have a Python app (main) that runs a script that requires Java code to be up to date. But the Java app (languagetool) doesn’t require updating when the Python app (main) is updated. In that case, you can nest the Java app within the Python app:

โ”œโ”€โ”€ .upsun
โ”‚   โ”œโ”€โ”€ .upsun/config.yaml
โ”œโ”€โ”€ languagetool
โ”‚   โ””โ”€โ”€           <- Java app code
โ””โ”€โ”€                 <- Python app code

The Python app’s code base includes all of the files at the top level (excluding the .upsun directory) and all of the files within the languagetool directory. The Java app’s code base includes only the files within the languagetool directory.

In this case, your .upsun/config.yaml file must contain 2 entries, one for the main app and second one for the languagetool app.

Once your repository is organized, you can use a configuration similar to the following:

      type: 'python:3.11'
          root: '/'

        type: 'java:17'
            root: 'languagetool'

Split your code source into multiple Git submodule repositories Anchor to this heading

If you have different teams working on different code with different processes, you might want each app to have its own repository. Then you can build them together in another repository using Git submodules.

With this setup, your apps are kept separate from the top application. Each app has its own Git submodule containing its code base. All your apps are configured in a single .upsun/config.yaml file. So you could organize your project repository like this:

โ”œโ”€โ”€ .upsun
โ”‚   โ”œโ”€โ”€ config.yaml
โ”œโ”€โ”€ @admin      <-- API Platform Admin submodule
โ”œโ”€โ”€ @api        <-- Bigfoot submodule
โ”œโ”€โ”€ @gatsby     <-- Gatsby submodule
โ”œโ”€โ”€ @mercure    <-- Mercure rocks submodule
โ””โ”€โ”€ .gitmodules

Add the submodules using the Git CLI.

Your .gitmodules file would define all the submodules like this:

[submodule "admin"]
	path = admin
	url =
[submodule "api"]
	path = api
	url =
[submodule "gatsby"]
	path = gatsby
	url =
[submodule "mercure"]
	path = mercure
	url =

Change the source root of your app Anchor to this heading

When your app’s code base and configuration file aren’t located at the same directory level in your project repository, you need to define a root directory for your app.

To do so, add a new source.root property in your app configuration.

For example, to change the source root of the admin app from the unified app configuration example project, you could add the following configuration:

    root: admin

The source.root path is relative to the repository root. In this example, the admin app now treats the admin directory as its root when building.

If source.root isn’t specified, it defaults to the project root directory, that is "/".

Is this page helpful?