# Multi-Environment Builds in io.Connect Seed Project

**URL:** <https://community.interop.io/t/multi-environment-builds-in-io-connect-seed-project/577>\
**Category:** io.Connect Desktop\
**Tags:** Knowledge-base, iocd-10\
**Created:** [May 12, 2026, 12:17pm UTC](https://community.interop.io/t/multi-environment-builds-in-io-connect-seed-project/577 "2026-05-12T12:17:31Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![toni.petrov](https://yyz2.discourse-cdn.com/flex028/user_avatar/community.interop.io/toni.petrov/32/17_2.png) [@toni.petrov](https://community.interop.io/u/toni.petrov)\
**Post date:** [May 12, 2026, 12:17pm UTC](https://community.interop.io/t/multi-environment-builds-in-io-connect-seed-project/577/1 "2026-05-12T12:17:31Z")

</div>

When deploying **io.Connect Desktop** across multiple environments (e.g., DEV, QA, UAT, PROD), you typically need different configurations, application URLs, and assets per environment. The seed project’s [modifications system](https://docs.interop.io/desktop/developers/seed-project/index.html#customization-modifications) provides the foundation for all approaches described below.

When you run `iocd build`, the CLI applies modifications in a specific order:

1. **`modifications/base/`** — Applied first, in all modes (dev and build).
2. **`modifications/build/`** — Applied second, only when building an installer.

Later modifications override earlier ones, which means that mode-specific customizations in `modifications/build/` are deep-merged on top of the shared base modifications. This means that to produce environment-specific builds, the question becomes: **how do you get the right files into `modifications/build/` before each build?**

Below are three approaches. You can also combine them (e.g., use Approach A for separate builds with unique assets, plus Approach C for runtime switching on developer machines).

### Approach A: Environment Folders with a Build Script

#### How It Works

You create a separate folder for each environment alongside `modifications/`. Each folder mirrors the same structure that would go inside `modifications/build/`. A build script copies the correct environment folder into `modifications/build/` before running `iocd build`.

#### Step 1 — Create the Folder Structure

```cmd
my-seed-project/
├── modifications/
│ ├── base/ # Shared across ALL environments
│ │ └── iocd/
│ │ ├── config/
│ │ │ └── system.json # Common system settings
│ │ └── apps/
│ │ └── shared-app.json
│ └── build/ # ← Left empty in source control
│
└── environments/ # One folder per environment
    ├── dev/
    │ └── iocd/
    │ ├── config/
    │ │ ├── system.json.merge # DEV-specific overrides
    │ │ └── logger.json.merge
    │ ├── apps/
    │ │ └── my-app.json.merge
    │ └── assets/
    │ └── images/ # DEV-specific icons/logos
    ├── qa/
    │ └── iocd/
    │ ├── config/
    │ │ ├── system.json.merge # QA-specific overrides
    │ │ └── logger.json.merge
    │ ├── apps/
    │ │ └── my-app.json.merge
    │ └── assets/
    │ └── images/
    ├── uat/
    │ └── iocd/...
    └── prod/
        └── iocd/...

```

#### Step 2 — Write Environment-Specific Override Files

Each file uses the `.json.merge` extension so it only overrides the fields that differ. For example:

`environments/dev/iocd/config/system.json.merge` — sets the DEV gateway port:

```json
{
    "gw": {
        "configuration": {
            "port": 8382
        }
    }
}

```

`environments/qa/iocd/config/system.json.merge` — sets the QA gateway port:

```json
{
    "gw": {
        "configuration": {
            "port": 8383
        }
    }
}

```

`environments/dev/iocd/config/apps/my-app.json.merge` — points to the DEV URL:

```json
{
    "details": {
        "url": "https://myapp-dev.example.com/"
    }
}

```

`environments/qa/iocd/config/apps/my-app.json.merge` — points to the QA URL:

```json
{
    "details": {
        "url": "https://myapp-qa.example.com/"
    }
}

```

For unique assets (icons, logos), place replacement files directly in the environment folder — e.g., `environments/prod/iocd/assets/images/logo.png`.

#### Step 3 — Create a Build Script

Create a build script that copies the right environment into `modifications/build/` before building.

> ⚠ _Note that this script requires a POSIX-compatible shell. On Windows, use Git Bash, WSL, or an equivalent shell to run it._

`scripts/build-env.sh`:

```bash
#!/bin/bash
ENV=$1

if [-z "$ENV"]; then
    echo "Usage: ./scripts/build-env.sh <dev|qa|uat|prod>"
    exit 1
fi

if [! -d "environments/$ENV"]; then
    echo "Error: Environment '$ENV' not found in environments/"
    exit 1
fi

echo "Building for environment: $ENV"

# 1. Clean previous build modifications.
# Note: This removes all contents of modifications/build/.
# If you have other build-specific modifications, place them in
# modifications/base/ instead, or merge them into the environment folders.
rm -rf modifications/build
mkdir -p modifications/build

# 2. Copy the environment-specific files into modifications/build/.
cp -r environments/$ENV/* modifications/build/

# 3. Build.
npx iocd build --output "dist/$ENV"

echo "Build complete: dist/$ENV"

```

#### Step 4 — Add npm Scripts

Add npm scripts to `package.json` for convenience:

```json
{
    "scripts": {
        "build:dev": "./scripts/build-env.sh dev",
        "build:qa": "./scripts/build-env.sh qa",
        "build:uat": "./scripts/build-env.sh uat",
        "build:prod": "./scripts/build-env.sh prod"
    }
}

```

#### What Happens at Build Time

When you run `npm run build:qa`, the following occurs:

1. The script clears `modifications/build/`.
2. It copies everything from `environments/qa/` into `modifications/build/`.
3. `iocd build` runs and applies modifications in order:
  - First: `modifications/base/` (shared settings e.g., common `system.json`).
  - Second: `modifications/build/` (now contains QA overrides e.g., `system.json.merge` with port 8383).

4. The QA merge files are deep-merged on top of the base, producing a final `system.json` with the QA-specific port.
5. The installer is output to `dist/qa/`.

### Approach B: Git Branches per Environment

#### How It Works

You maintain a `main` branch with the shared base configuration. For each environment, you create a long-lived branch where `modifications/build/` contains that environment’s specific overrides. Building is simply a matter of checking out the right branch and running `iocd build`.

#### Step 1 — Set Up the Main Branch

On `main`, configure `modifications/base/` with settings shared by all environments. The `modifications/build/` folder is either empty or absent.

```cmd
main branch:
├── modifications/
│ ├── base/
│ │ └── iocd/
│ │ ├── config/
│ │ │ └── system.json # Common settings
│ │ └── apps/
│ │ └── shared-app.json
│ └── build/ # Empty on main

```

#### Step 2 — Create Environment Branches

```cmd
git checkout main
git checkout -b env/dev

```

#### Step 3 — Add Environment-Specific Files

Add environment-specific files to `modifications/build/` on that branch:

```cmd
env/dev branch:
├── modifications/
│ ├── base/ # Inherited from main
│ │ └── iocd/...
│ └── build/ # DEV-specific overrides
│ └── iocd/
│ ├── config/
│ │ ├── system.json.merge # port: 8382
│ │ └── logger.json.merge
│ ├── apps/
│ │ └── my-app.json.merge
│ └── assets/
│ └── images/ # DEV icons

```

The merge files use the same format as in Approach A:

`modifications/build/iocd/config/system.json.merge`:

```json
{
    "gw": {
        "configuration": {
            "port": 8382
        }
    }
}

```

`modifications/build/iocd/config/apps/my-app.json.merge`:

```json
{
    "details": {
        "url": "https://myapp-dev.example.com/"
    }
}

```

Commit these files and repeat for `env/qa`, `env/uat`, and `env/prod`.

#### Step 4 — Build from the Appropriate Branch

```cmd
git checkout env/qa
npx iocd build

```

No scripts needed — the correct merge files are already in `modifications/build/` on that branch.

#### Keeping Branches Up to Date

When shared configuration changes on `main` (e.g., a new app is added to `modifications/base/`, or a component is updated), you need to pull those changes into each environment branch:

```cmd
git checkout env/qa
git rebase main
# If there are conflicts in modifications/build/, resolve them.
git push --force-with-lease

```

> ⚠ _Note that you must do this for every environment branch whenever `main` changes._

### Approach C: Single Build with Config Overrides

#### How It Works

Instead of building separate installers per environment, you build **once** and bundle all environment-specific config override files into the package. Users launch different environments via **shortcuts** that pass different command-line arguments to the same executable.

**io.Connect Desktop** supports a `configOverrides` command-line protocol:

```cmd
io-connect-desktop.exe -- config=config/system.json configOverrides config0=config/overrides/system-override-QA.json

```

Key rules:

- Overrides are numbered `config0` through `config9` (up to 10).
- Higher numbers are merged onto lower ones (e.g., `config3` merges onto `config2`, which merges onto `config1`, etc.).
- The base `config=` file is loaded first, then overrides are applied on top.

This means you can have a single installation with multiple shortcuts - one per environment - each pointing to the same executable but with different override arguments.

#### Step 1 — Create Environment-Specific Override Files

Place override JSON files in a config overrides directory via modifications. Each file contains only the settings that differ from the base `system.json`:

```cmd
modifications/
├── base/
│ └── iocd/
│ ├── config/
│ │ └── system.json # Base config (shared)
│ └── apps/
│ └── my-app.json # Base app config
│
└── build/
    └── iocd/
        └── config/
            └── overrides/
                ├── system-override-DEV.json
                ├── system-override-QA.json
                ├── system-override-UAT.json
                └── system-override-PROD.json

```

`config/overrides/system-override-DEV.json`:

```json
{
    "gw": {
        "configuration": {
            "port": 8382
        }
    }
}

```

`config/overrides/system-override-QA.json`:

```json
{
    "gw": {
        "configuration": {
            "port": 8383
        }
    }
}

```

You can also layer overrides. For example, if DEV and QA share some settings but differ in others, use multiple override files:

```cmd
-- config=config/system.json configOverrides config0=config/overrides/shared-non-prod.json config1=config/overrides/system-override-DEV.json

```

Here `config1` (DEV-specific) merges onto `config0` (shared non-prod settings), which merges onto the base `system.json`.

#### Step 2 — Handle Application-Specific Overrides

For app configurations that differ per environment (e.g., different URLs), you have two options:

- Bundle all environment app configs and reference them via `configOverrides` (if the application definition supports override loading).
- Include environment-specific application definition files in the overrides, and use separate `system.json` override files per environment that point to different app definitions.

#### Step 3 — Create Environment-Specific Shortcuts

After the build, create Windows shortcuts (`.lnk`) that launch the executable with different arguments:

**PROD shortcut** (default — no overrides, or explicit PROD override):

```cmd
"C:\...\io-connect-desktop.exe" -- config=config/system.json configOverrides config0=config/overrides/system-override-PROD.json

```

**QA shortcut:**

```cmd
"C:\...\io-connect-desktop.exe" -- config=config/system.json configOverrides config0=config/overrides/system-override-QA.json

```

**DEV shortcut:**

```cmd
"C:\...\io-connect-desktop.exe" -- config=config/system.json configOverrides config0=config/overrides/system-override-DEV.json

```

These shortcuts can be:

- Created manually by the end user or IT team after installation.
- Distributed alongside the installer as `.lnk` files.
- Generated by a post-install script.

#### What Happens at Runtime

When a user clicks the “QA” shortcut:

1. The exe launches and loads the base `config/system.json`.
2. It sees `configOverrides` and loads `config0=config/overrides/system-override-QA.json`.
3. The QA override is deep-merged onto the base system config.
4. The platform starts with the QA-specific port, URLs, and settings.

#### Limitations

> ⚠ _Note the following limitations of this approach:_
> 
> - **Assets** : Binary assets (icons, logos) can’t differ per environment because the same build is shared. If environments need different visual branding, combine this with Approach A or B for the asset differences.
> - **Shortcut distribution** : The seed project’s installer generates a single default shortcut. Additional shortcuts must be created separately (manually, via script, or via Group Policy / MDM deployment).
> - **App definitions** : Application-level configs (like app URLs) may need to follow the same override pattern, or you may need environment-specific app definition files bundled and selected via overrides.

### Comparison

| | Approach A (Script) | Approach B (Branches) | Approach C (Config Overrides) |
| --- | --- | --- | --- |
| **Build count** | One per environment | One per environment | Single build for all environments |
| **Visibility** | All env configs visible in one place — easy to compare and audit | Configs spread across branches — must switch branches to inspect | All override files visible in one place |
| **Maintenance** | Change base config once, all envs pick it up automatically | Must rebase every environment branch after base changes | Base changes auto-apply |
| **Merge conflicts** | None — environment folders don’t overlap | Possible when rebasing, especially if base and env modify the same files | None |
| **Asset differences** | Fully supported | Fully supported | Not supported (same build) |
| **Deployment** | One installer per env | One installer per env | One installer + shortcuts per env |
| **Runtime flexibility** | None (baked at build time) | None (baked at build time) | Users can switch envs without reinstalling |
| **Scalability** | Adding an environment = adding a folder | Adding an environment = creating and maintaining another long-lived branch | Adding an environment = adding an override file + shortcut |
| **Complexity** | Low (script + folders) | Medium (Git rebasing) | Low-Medium (override files + shortcut distribution) |

**Choosing an approach:**

- **Approach A** — Best when environments are structurally similar (same apps, same configs, just different values), your base config changes frequently, or you want to easily compare settings across environments.
- **Approach B** — Best when environments diverge significantly (different apps or components per environment), your team is comfortable with Git rebasing, or you need an independent audit trail per environment.
- **Approach C** — Best when environments differ only in configuration (not visual assets), you want a single installer, or you need users/IT to be able to switch environments without reinstalling. This is also useful when the same machine needs access to multiple environments.
