Repository: awslabs/seed-farmer (language: python)

Issue:
Wip/2.0.0
Upgrading seedfarmer to version 2.0.0 



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 395d1894..5d3a99b3 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -5,7 +5,7 @@ on:
   push:
     branches: [ "main" ]
   pull_request:
-    branches: [ "main", "release/*", "stable" ]
+    branches: [ "main", "release/*", "stable", "wip/*" ]
 
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
@@ -19,7 +19,7 @@ jobs:
         python-version: [3.9]
     defaults:
       run:
-        working-directory: seedfarmer
+        working-directory: .
     steps:
       - uses: actions/checkout@v2
       - name: Set up Python ${{ matrix.python-version }}
@@ -29,13 +29,16 @@ jobs:
       - name: Install Requirements
         run: |
           python -m pip install --upgrade pip
-          pip install -r ../requirements.txt
-          pip install -r ../requirements-dev.txt
+          pip install -r requirements.txt
+          pip install -r requirements-dev.txt
+          pip install -e .
       - name: Mypy Check
-        run: mypy --ignore-missing-imports .
+        run: mypy --ignore-missing-imports ./seedfarmer
       - name: Flake8 Check
-        run: flake8 .
+        run: flake8 ./seedfarmer
       - name: Black Check
-        run: black --check .
+        run: black --check ./seedfarmer
       - name: Isort Check
-        run: isort --check .
+        run: isort --check ./seedfarmer
+      - name: PyTest
+        run: cp test/unit-test/mock_data/seedfarmer.yaml . && pytest test/unit-test/test_models.py -v --cov=seedfarmer
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcbd33a6..8462e341 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,21 +5,53 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch
 
 
 
-## Unreleased
+## v2.0.0 (2022-09-22)
 
 ### New
+- new CLI bootstrap commands for Toolchain and Target accounts
+- create SessionManager class for supporting multi-account, multi-region
+- bootstrap command support to generate CFN templates for Toolchain and Target accounts
+- added deployment for toolchain and target accounts via CFN
+- support for cross-account and cross-region deployments
+- support for envVariable as valueFrom via .env and python-dotenv
+- threaded the priming of accounts on create and destroy
+- added destroy of managed polices when destroying deployments
+- threaded module info fetching
+- added account and region to module list output
+- added region and profile support for CLI commands
+- added multi-region support for list moduledata
+- support ParameterStore and SecretsManager as sources for module parameters (new valueFrom types)
+- added region adn profile support for all cli commands where needed
+
 
 ### Changes
+- update DeploymentManifest to support targetAccountMappings and regionMappings
+- update ModuleManifest to support targetAccount and targetRegion with defaults
+- move deployment level Parameters (dockerCredentialsSecret, permissionsBoundaryName) to mappings
+- refactor cli commands/groups to reduce line count in `__main__.py`
+- moved projectpolicy.yaml into resources/.
+- added profile and region support for session in `_session_utils.py`
+- convertd `session_manager.py` to only use `_session_utils.py`
+- refactored deployment_command objects and signatures for threadding
+
 ### Fixes
+- fix import failure of seedfarmer top-level module if seedfarmer.yaml doesn't exist
+- enable basic CLI functions by lazy loading seedfarmer.yaml and boto3.Session
+- ensure get_account_id() and get_regin() always use correct boto3.Session
+- ensure bootstrap functions look for roles and cfn templates when updating/deploying roles
+- honed back deployment role permissions
+- modified session manager to support threadding with the toolchain session
+- rename manifest parameter permissionBoundaryArn -> permissionsBoundaryName to align on AWS naming and hide account ids in ARNs
+- ensure we find a deploymed manifest when listing/printing module metadata
 
 ## v0.1.4 (2022-08-16)
-### New
 
 ### Changes
 - updated user-friendly messages for invalid credentials / sessions
 - added version support for CLI `seedfarmer version`
 - updated dependency aws_codeseeder==0.4.0
 - corrected import of CodeSeederRuntimeError
+
 ### Fixes
 - allow CLI to return when exporting moduledata that is None
 - remove table and short url for errored codebuild urls
@@ -30,7 +62,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch
 ### New
 - support logging output and url gereration from codebuild output / codeseeder
 - added use of CodeSeederRuntimeError
-- added ModuleDeploymentResponse object 
+- added ModuleDeploymentResponse object
 - added export of moduledata (metadata) in UNIX format from CLI (--export-local-env)
 
 ### Changes
@@ -51,7 +83,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch
 ### Changes
 
 ### Fixes
-- use HTTPS instead off SSH for git cloning 
+- use HTTPS instead off SSH for git cloning
 
 
 ---
@@ -66,7 +98,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch
 - lock versions in requirements-dev.in
 - updating CLI output to discern changes to manifest, intended deployment
 - lazy-load seedkit (version 0.3.2 of codeseeder)
-- improve logging messages from codeseeder 
+- improve logging messages from codeseeder
 - align message output / tables listing modules requested, modified, deleted for deployment
 
 ### Fixes
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..b9208dc4
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,1 @@
+recursive-include seedfarmer/resources *
\ No newline at end of file
diff --git a/VERSION b/VERSION
index 446ba66e..359a5b95 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.1.4
\ No newline at end of file
+2.0.0
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 83535898..8adae57f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -6,4 +6,9 @@ To rebuild the documentation:
 cd docs/
 make clean
 make html
+```
+
+these python libraries need to be installed in the virtual env:
+```bash
+pip install sphinx myst_parser sphinx-autoapi sphinx_rtd_theme docutils==0.15 seed-farmer
 ```
\ No newline at end of file
diff --git a/docs/source/_static/multi-account.png b/docs/source/_static/multi-account.png
new file mode 100644
index 00000000..ee702888
Binary files /dev/null and b/docs/source/_static/multi-account.png differ
diff --git a/docs/source/_static/seedfarmer_diagrams.pptx b/docs/source/_static/seedfarmer_diagrams.pptx
new file mode 100644
index 00000000..f0af8cf4
Binary files /dev/null and b/docs/source/_static/seedfarmer_diagrams.pptx differ
diff --git a/docs/source/architecture.md b/docs/source/architecture.md
new file mode 100644
index 00000000..681061c9
--- /dev/null
+++ b/docs/source/architecture.md
@@ -0,0 +1,25 @@
+# Architecture
+
+**Seed-Farmer** does not create its own deployments, rather it helps to deploy YOUR modules by acting as the broker between your 
+module code and the AWS Cloud via AWS CodeSeeder.
+
+
+## Multi-Account Architecture
+![MultiAccount seedfarmer](_static/multi-account.png)
+
+Please see these [account definitons](multiaccount_support) for details.
+
+## Method of Processing
+Below is a flow of `seedfarmer` deployment of a single module in a single account/region.  Once `seedfarmer` resolves the proer target/deployment role, the following depicts the steps taken to deploy a module.
+![seedfarmer Invocation](_static/SeedFarmer.png)
+
+1. Invoke **seedfarmer** CLI
+2. **seedfarmer** reads/writes deployment metadata with AWS Systems Manager
+3. **seedfarmer** invokes AWS IAM to create module-specific roles, attaching the proper least-privilege policies
+4. **seedfarmer** leverages **AWS CodeSeeder** for remote deployment on AWS CodeBuild
+5. **AWS CodeSeeder** prepares AWS CodeBuild 
+6. AWS CodeBuild via **AWS CodeSeeder** inspects and fetches data from AWS SecretsManager (if necessary)
+7. AWS CodeBuild via **AWS CodeSeeder** executes the custom **deployspec** for the module
+9. AWS CodeBuild via **AWS CodeSeeder** updates AWS Systems Manager with completed module metadata
+9. **seedfarmer** updates deployment metadata in AWS Systems Manager
+
diff --git a/docs/source/bootstrapping.md b/docs/source/bootstrapping.md
new file mode 100644
index 00000000..897b9abe
--- /dev/null
+++ b/docs/source/bootstrapping.md
@@ -0,0 +1,64 @@
+# Bootstrapping Accounts
+
+You must have only one `toolhchain account` bootstrapped and at least one `target account` bootstrapped.  The account that is the `toolhchain account` can be bootstrapped as a `target account`.
+
+
+## Bootstrap Toolchain Account
+The `seedfarmer bootstrap toolchain` CLI command will take care of setting up the account.  
+```bash
+Usage: seedfarmer bootstrap toolchain [OPTIONS]
+
+  Bootstrap a Toolchain account.
+
+Options:
+  -p, --project TEXT              Project identifier
+  -t, --trusted-principal TEXT    ARN of Principals trusted to assume the
+                                  Toolchain Role
+  -b, --permissions-boundary TEXT  ARN of a Managed Policy to set as the
+                                  Permission Boundary on the Toolchain Role
+  --as-target / --not-as-target   Optionally also bootstrap the account as a
+                                  Target account  [default: not-as-target]
+  --synth / --no-synth            Synthesize a CFN template only...do not
+                                  deploy  [default: no-synth]
+  --profile TEXT                  The AWS profile to initiate a session
+  --region TEXT                   AWS region to use
+  --debug / --no-debug            Enable detail logging  [default: no-debug]
+  --help                          Show this message and exit.
+```
+
+The `trusted-principal` field allows you to pass in one or multiple roles who CAN assume the `toolchain account` role.  If you do not pass in at least one, no one can assume this role (that is bad).
+
+The `permission-boundary` filed allows you to attach a policy to the role to act as a [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html)
+
+Typically, you can have the `toolchain account` act as the `target account`.  The `as-target` field will bootstrap both in a single command. 
+
+
+
+## Bootstrap Target Account
+
+```bash
+sage: seedfarmer bootstrap target [OPTIONS]
+
+  Bootstrap a Target account.
+
+Options:
+  -p, --project TEXT              Project identifier
+  -t, --toolchain-account TEXT    Account Id of the Toolchain account trusted
+                                  to assume the Target account's Deployment
+                                  Role  [required]
+  -b, --permissions-boundary TEXT  ARN of a Managed Policy to set as the
+                                  Permission Boundary on the Toolchain Role
+  --synth / --no-synth            Synthesize a CFN template only...do not
+                                  deploy  [default: no-synth]
+  --profile TEXT                  The AWS profile to initiate a session
+  --region TEXT                   AWS region to use
+  --debug / --no-debug            Enable detail logging  [default: no-debug]
+  --help                          Show this message and exit.
+```
+
+You must pass in the `toolchain-account` field so a trust-relationship can be set up between the `toolchain account` role and the `target account` role.
+
+The `permission-boundary` filed allows you to attach a policy to the role to act as a [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html)
+
+## Prepping the Account / Region
+`seedfarmer` leverages the AWS CDKv2.  This must be bootstrapped in ech account/region combination to be used of each target account.
\ No newline at end of file
diff --git a/docs/source/cli_commands.md b/docs/source/cli_commands.md
index 97e84004..16b1943f 100644
--- a/docs/source/cli_commands.md
+++ b/docs/source/cli_commands.md
@@ -1,17 +1,17 @@
-## SeedFarmer CLI (Command Line Interface)
+# CLI (Command Line Interface)
 
-The SeedFarmer CLI provides the primary way to interface with the orchestration framework that manages a deploymement with AWS CodeSeeder.  It is used by CICD pipelines and individual users to:
+The `seedfarmer` CLI provides the primary way to interface with the orchestration framework that manages a deploymement with AWS CodeSeeder.  It is used by CICD pipelines and individual users to:
  - deploy code (modules) via a deployment and manifest
  - fetch metadata related to currently deployed modules
  - destroy deployments
  - apply changes to deployments (via a GitOps model)
 
-### Summary Commands
+## Summary Commands
 These commands are  structured in the format of:
 ```
 seedfarmer <verb> <object> -<parameters>
 ```
-#### Top Level Commands:
+### Top Level Commands:
 ```
 > seedfarmer
 Usage: seedfarmer [OPTIONS] COMMAND [ARGS]...
@@ -35,7 +35,7 @@ Each sub-command has help information related to how to use them.  Users typical
 
 
 
-#### Example Walkthru - Deploy and Apply changes
+### Example Walkthru - Deploy and Apply changes
 The first time deploying, a deployment manifest must be created [see here](manifests.md).  The deployment manifests should be in the ```manifests``` directory.  All paths are relative to the project level (at all times).  For this example, we have a deployment manifest  located at ```manifests/walkthru/deployment.yaml``` and and modules manifests located at ```manifests/walkthru/<whatevername>```.  To check our deployment without apply any changes, our command would look like this:
 ```
 seedfarmer apply manifests/walkthru/deployment.yaml --dry-run
@@ -59,58 +59,3 @@ Once we are finished with the deployment (ex. the ```local``` deployment), we ca
 seedfarmer destroy local
 ```
 
-
-
-#### Example Walkthru - Fetch Module Metadata
-
-This is an example of how to get the metadata of a module without knowing what is currently deployed.
-
-First, get the list of all deployments in this region:
-```
-> seedfarmer list deployments
-Deployment    
-Names         
-┏━━━━━━━━━━━━┓
-┃ Deployemnt ┃
-┡━━━━━━━━━━━━┩
-│ local      │
-└────────────┘
-```
-Here, there is one deployment: _local_ <br>
-Now, get all the modules deployed in _local_ with their corresponding group:
-
-
-```
-> seedfarmer list modules -d local
-Deployed Modules                                     
-┏━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
-┃ Deployemnt ┃ Group       ┃ Module                 ┃
-┡━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
-│ local      │ optionals   │ networking             │
-│ local      │ optionals   │ datalake-buckets       │
-│ local      │ core        │ metadata-storage       │
-│ local      │ core        │ opensearch             │
-│ local      │ core        │ eks                    │
-│ local      │ blogs       │ rosbag-scene-detection │
-│ local      │ blogs       │ rosbag-webviz          │
-│ local      │ integration │ opensearch-proxy       │
-│ local      │ integration │ rosbag-ddb-to-os       │
-└────────────┴─────────────┴────────────────────────┘
-```
-
-With the above table, we can fetch all the metadata associated with a module deployment.  The output is in JSON:
-
-```
-> seedfarmer list moduledata -d local -g core -m metadata-storage
-{
-  "GlueDBName": "project-local-core-metadata-storage-vsidata",
-  "RosbagBagFilePartitionKey": "bag_file_prefix",
-  "RosbagBagFileTable": "project-local-core-metadata-storage-Rosbag-BagFile-Metadata",
-  "RosbagSceneMetadataPartitionKey": "bag_file",
-  "RosbagSceneMetadataSortKey": "scene_id",
-  "RosbagSceneMetadataStreamArn": "arn:aws:dynamodb:us-east-1:123456789012:table/project-local-core-metadata-storage-Rosbag-Scene-Metadata/stream/2022-03-23T17:25:25.958",
-  "RosbagSceneMetadataTable": "project-local-core-metadata-storage-Rosbag-Scene-Metadata"
-}
-
-```
-This metadata is the same metadata that modules reference (as needed).
\ No newline at end of file
diff --git a/docs/source/cookiecutter.md b/docs/source/cookiecutter.md
index 784aed19..ac70a500 100644
--- a/docs/source/cookiecutter.md
+++ b/docs/source/cookiecutter.md
@@ -1,11 +1,7 @@
-# Cookiecutter & Project Templating
+# Templating with Cookiecutter
 
 The `SeedFarmer CLI` used [CookieCutter](https://cookiecutter.readthedocs.io/en/stable/) to simplify the initialization of new project and modules in existing projects.  
 
-
-* [Create a New Project](cookiecutter_new_project)
-* [Create a New Module](cookiecutter_new_module)
-
 ## What is Cookiecutter
 Cookiecutter is a command-line utility that creates projects from project templates(known as cookiecutters). The project templates are housed in a git repository where the files, directories, and the content of both, can be templated. All templating is done with Jinja2. The repository contains a `cookiecutter.json` that containes keys:values that will be used by the templating engine when intialized. The initialization can be interactive if you want to override the `cookiecutter.json` values. In non-interactive mode, the values in cookiecutter.json are default.
 
@@ -63,53 +59,10 @@ Please take a look at the [Cookiecuter Github](https://github.com/cookiecutter/c
 ## SeedFarmer and Cookiecutter
 `seedfarmer` integrates the Cookiecutter library right out of the box to create projects and modules. The template repo structure differs for a project and module, and are housed in separate branches in this repo: `init-project` and `init-module`. Those branches are defaults for `seedfarmer init` when using our repo for templating. If you'd like to use another cookiecutter repo for initialization, you can override the template url. Keep in mind, if you do override the template url, make sure you follow the seedfarmer deployment guide; The default template creates some of the structure specific for seedfarmer that won't be in other custom templates.
 
-(cookiecutter_new_project)=
-## How to Create a New Project
-Verify that you have `seedfarmer` installed from PyPi. 
-```
-pip list | grep seed-farmer
-```
-If not installed, please install:
-```
-pip install seed-farmer
-```
-On your local compute resource, pick a directory and add a file named `seedfarmer.yaml` that has the name of your new project.  For example, a new project named `mynewproject` can be seeded via the following:
-```bash
-echo project: mynewproject > seedfarmer.yaml
-```
 
 
-Now you are ready to create the new project.  
-
-```bash
-seedfarmer init project --help
-Usage: seedfarmer init project [OPTIONS]
-
-  Initialize a project. Make sure seedfarmer.yaml is present in the same
-  location you execute this command!!
-
-Options:
-  -t, --template-url TEXT  The template URL. If not specified, the default
-                           template repo is `https://github.com/awslabs/seed-
-                           farmer`
-  --help                   Show this message and exit.
-```
-This example will create a new directory in your current working directory that contains some bare structure to support development with seedfarmer:
-
-```bash
-seedfarmer init project
-```
-Your project is created and configured to use the `seedfarmer` CLI.
-
-If you want to use a different project template, you can override the default template url. For example:
-```
-seedfarmer init project -t https://github.com/briggySmalls/cookiecutter-pypackage
-```
-NOTE: your project template for a new `seedfarmer` project  must contain at least the required project sturcture.
-
-
-(cookiecutter_new_module)=
-## How to Create a New Module
+(indepth_module_creation)=
+### Create the Module Skeleton
 Within a `seedfarmer` project, you can create new modules. Below are the module initialization commands.
 ```
 # seedfarmer init module --help
@@ -150,9 +103,11 @@ Nested grouping is also supported. An example that would create the directory st
 seedfarmer init module -g "analytics/streaming" -m kinesis
 ```
 
+`seedfarmer` uses [CookieCutter](cookiecutter.md)for templating.
 If you want to use a different module template, you can override the default template url. For example:
 ```
 seedfarmer init module -m my-module-name -t https://github.com/briggySmalls/cookiecutter-pypackage
 ```
-NOTE: your project template for modules must contain at least the required files for a `seedfarmer` module.
+
+
 
diff --git a/docs/source/deployment_guide.md b/docs/source/deployment_guide.md
index 9451dc3a..7a5ed236 100644
--- a/docs/source/deployment_guide.md
+++ b/docs/source/deployment_guide.md
@@ -1,126 +1,77 @@
-# Deployment Guide
+# Deployment Step-By-Step
 
-Seedfarmer follows the Industry standard `GitOps` model. The project is entirely driven by `Descriptive` prinicples in the form of asking inputs from a module developer via manifest file(s).
+This is a logical step-by-step guide to create and deploy a `seedfarmer` project.
 
-## Steps to deployment
-
-#### Create a Virtual environment using:
+## Create a Virtual environment
 
 ```bash
 python3 -m venv .venv
 source .venv/bin/activate
 ```
 
-####  Install the requirements
+##  Install the requirements
 
 ```bash
-pip install seed-farmer.
+pip install seed-farmer
 ```
 
-#### Create a mew project
-The following command will create the skeleton of your project
-```bash
-seedfarmer init project -n myapp
-```
-
-Note that a file called `seedfarmer.yaml` has been created. That contains basic configuration that seedfarmer will use. For example, the file contains a key `project` that will prefex resource names upon creation
-
-
-#### Setting the region
-Seedfarmer submits build information to AWS CodeBuild via AWS CodeSeeder.  The initial submittal is done via AWS CLI, leveraging the configured active AWS profile.  If you would like to deploy your project to a region other than the region configured for the profile, you can explicitly set the region via:
+## Bootstrap the Toolchain and Target Accounts
+Please see [Bootstrapping](bootstrapping)
 
-```bash
-export AWS_DEFAULT_REGION=<region-name>   (ex. us-east-1, eu-central-1)
-```
-Please see [HERE for details](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html)
-
-#### Bootstrap the CDK
-We use AWS CDK V2 as the standard CDK as it is compatible with V1.  But, you need to bootstrap it (one time per region) with V2 - see [HERE](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html).
+## Bootstrap the CDK in Each Account/Region
+`seedfarmer` uses the concepts of `toolchain accounts` and `target accounts`.  `Modules` can deployed in any account/region combination.  
+Identify EACH account/region of the `target accounts` to be used and bootstrap the AWS CDK in each region.
+We use AWS CDK V2 as the standard CDK as it is compatible with V1 - see [HERE](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html).
 ```bash
 cdk bootstrap aws://<account>/<region>
 ```
 
 
-#### Prep the AWS SecretsManager
-As part of this example deployment (`example-dev`), there are modules that leverage credentials for access.  We store these credentials in AWS SecretsManager.  We have provided a bash script to populate AWS SecretsManager with default values for those modules that need them located in at `scripts/setup-secrets-example.sh`.  
-Run the bash script from commandline with the following:
-```bash
-source scripts/setup-secrets-example.sh
-```
-> NOTE the above script uses `jq`.  Please install it via [these instructions](https://stedolan.github.io/jq/download/).
-#### Verify the Manifests for Deployment
-Move to the `manifests` directory using the below command, where you will locate sample manifests in the name of `example-dev` and `example-prod`. You should create a directory with your desired deployment names like `uat`, `demo` etc.
-
-```bash
-cd manifests/
-```
-
-### Deployment WalkThrough
+## Create a New Project or Clone Existing
+You have the option of either:
+1. Creating a new project (see [Project Development](project_development.md)
+2. Cloning an existing `seedfarmer` compliant Git Repo
 
-For the below walkthrough, let us move into the `example-dev` directory using the below command, where the deploymemt name is set to `example-dev`:
+If cloning, skip to [Deploying Modules](deploying_modules)
 
-```bash
-cd example-dev/
-```
 
-File `deployment.yaml` is the top level manifest, which should include the modules you wanted to deploy, grouped under logical containers called `group`.  Please see [manifests](manifests.md) for more details.
+## Add Dockerhub Support if needed
+To avoid throttling from `DockerHub` when building images, you should create a parameter/re-use an existing parameter in `AWS Secrets Manager` to store your DockerHub username and password.  There is a corresponding `manifest` parameter that corresponds to the AWS Secret ([see here](deployment_manifest)).
 
-> Note:
-> All paths inside the manifest files should be relative to the root of the project. For ex: if you want to include a module manifest `example-modules.yaml` in your deplopyment.yaml, you should declare the path as `manifests/example-dev/example-modules.yaml`
+## Create New Project
+Please see [Project Development](project_development)
 
-#### Dockerhub Support
+## Create Modules
+Please see [Module Development](module_development)
 
-To avoid throttling from `DockerHub` when building images, you should create a parameter/re-use an existing parameter in `AWS Secrets Manager` to store your DockerHub username and password. 
 
-To create/update the default secret (aws-myapp-docker-credentials) run:
+(deploying_modules)=
+## Deploy Modules
 
+Manifests should be lcoated at <<project>>/manifests/*.  Change to the project rood directory
 ```bash
-./scripts/setup-secrets-dockerhub.sh
+cd <<project_root>>
 ```
 
-> Note:
-> For additional info on setting up the secret manually see [manifests](manifests.md) guide for details.
-> The `examples-dev` modules DO leverage DockerHub, so you should populate the SecretsManager as indicated in [manifests](manifests.md).
-
-For the walkthrough, we have few manifests declared within `example-dev` directory which installs the following modules in sequence. If you do not wish to know what modules the demo deployment is doing, you can skip reading this section:
-  * `Group` name is set to `optionals` to install the following, sourcing from the manifest `manifests/example-dev/optional-modules.yaml`:
-    * Creates a `networking` module (creates vpc, 2 Public/Private subnets, IGW, NAT, Gateway endpoints etc)
-    * Creates a `datalake-buckets` module (creates shared buckets for datalake, logging and artifacts etc)
-  * `Group` name is set to `core` to install the following, sourcing from the manifest `manifests/example-dev/core-modules.yaml`:
-    * Creates `eks` module (creates AWS EKS Compute environment with standard plugins installed)
-    * Creates `mwaa` module(creates AWS Managed Airflow cluster for orchestration of dags)
-    * Creates `metadata-storgae` module(creates shared AWS DynamoDB and AWS Glue databases)
-    * Creates `opensearch` module (creates AWS Managed Opensearch for ingesting app/infra logs)
-  * `Group` name is set to `examples` to install the following, sourcing from the manifest `manifests/example-dev/example-modules.yaml`:
-    * Creates `example-dags` module (Demos an example on how to deploy dags from target modules using the shared mwaa module)
-  * `Group` name is set to `blogs` to install the following, sourcing from the manifest `manifests/example-dev/blogs.yaml`:
-    * Creates `rosbag-scene-detection` module
-    > Note: This was previously named as `module1`
-    * Creates `rosbag-visualization` module
-    > Note: This was previously named as `module5`
-  * `Group` name is set to `integration` to install the following, sourcing from the manifest `manifests/example-dev/integration-modules.yaml`:
-    * Creates `rosbag-scene-detection` module
-
-#### Deploy
 Use the [CLI](cli_commands.md) to deploy.
 Below is the command to deploy the modules using the main manifest `deployment.yaml`:
 
 ```bash
-seedfarmer apply manifests/example-dev/deployment.yaml
+seedfarmer apply manifests/<<somename>>/deployment.yaml
 ```
 
+## Destroy Modules
+`seedfarmer` uses the manifests and GitOps.  To `destroy` a module but leave the other modules intact, just comment out the module definition and apply
+```bash
+seedfarmer apply manifests/<<somename>>/deployment.yaml
+```
 
-
-#### Destroy
+## Destroy Deployment
 Below is the command to destroy all the modules related to a deployment:
 
 ```bash
 seedfarmer destroy <<DEPLOYMENT_NAME>>
 ```
 
-> Note:
-> Replace the `DEPLOYMENT_NAME` with the desired deployment name of your environment. For ex: `example-dev`
-> You can pass an optional `--debug` flag to the above command for getting debug level output
 
-### FAQS
 
diff --git a/docs/source/deployspec.md b/docs/source/deployspec.md
deleted file mode 100644
index 447506c3..00000000
--- a/docs/source/deployspec.md
+++ /dev/null
@@ -1,95 +0,0 @@
-## Deployspec
-
-Each Module must contain a deployspec.yaml file. This file defines deployment instructions read by seedfarmer. These instructions include the external module metadata required, libraries/utilities to be installed, and deployment commands. The deployspec.yaml is very similar to the AWS CodeBuild [buildspec.yaml](https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html) implementing the phases structure and adding a module_dependencies section for declaring other modules whose metadata should be made to the module on deployment.
-
-### Structure
-Below is a sample manifest that just 'echo' data to the environment runtime:
-
-```yaml
-deploy:
-  phases:
-    install:
-      commands:
-      - pip install -r requirements.txt
-    pre_build:
-      commands:
-      - echo "Prebuild stage"
-    build:
-      commands:
-      - echo "bash deploy.sh"
-    post_build:
-      commands:
-      - echo "Deploy successful"
-destroy:
-  phases:
-    install:
-      commands:
-      - pip install -r requirements.txt
-    pre_build:
-      commands:
-      - echo "Prebuild stage"
-      - echo "testing change"
-    build:
-      commands:
-      - echo "DESTROY!"
-    post_build:
-      commands:
-      - echo "Destroy successful"
-build_type: BUILD_GENERAL1_LARGE
-```
-
-The deployspec is broken into 2 major areas of focus: `deploy `and `destroy`.  Each of these areas have 4 distinct phases in which commands can be executed (ex. installing supporting libraries, setting environment variables, etc.)  It is in these sections that AWS CodeSeeder makes calls to deploy/destroy on the modules' behalf.  The example below will highlight.
-
-The parameter `build_type` allows module developers to choose the size of the compute instance AWS CodeSeeder will leverage as defined [HERE](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html).  This parameter is defaulted to `BUILD_GENERAL1_SMALL`
-
-The currently supported values are:
-```
-- BUILD_GENERAL1_SMALL
-- BUILD_GENERAL1_MEDIUM
-- BUILD_GENERAL1_LARGE 
-- BUILD_GENERAL1_2XLARGE
-```
-
-
-### Example
-The following is an example deployspec that issues a series of commands.  This is only an example...
-
-```yaml
-deploy:
-  phases:
-    install:
-      commands:
-        - npm install -g aws-cdk@2.20.0
-        - apt-get install jq
-        - pip install -r requirements.txt
-    build:
-      commands:
-        - aws iam create-service-linked-role --aws-service-name elasticmapreduce.amazonaws.com || true
-        - export ECR_REPO_NAME=$(echo $MYAPP_PARAMETER_FARGATE | jq -r '."ecr-repository-name"')
-        - aws ecr describe-repositories --repository-names ${ECR_REPO_NAME} || aws ecr create-repository --repository-name ${ECR_REPO_NAME}
-        - export IMAGE_NAME=$(echo $MYAPP_PARAMETER_FARGATE | jq -r '."image-name"')
-        - export COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
-        - export IMAGE_TAG=${COMMIT_HASH:=latest}
-        - export REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPO_NAME
-        - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
-        - >
-          echo "MYAPP_PARAMETER_SHARED_BUCKET_NAME: ${MYAPP_PARAMETER_SHARED_BUCKET_NAME}"
-        - echo Building the Docker image...          
-        - cd service/ && docker build -t $REPOSITORY_URI:latest .
-        - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
-        - docker push $REPOSITORY_URI:latest && docker push $REPOSITORY_URI:$IMAGE_TAG
-        - cd .. && cdk deploy --all --require-approval never --progress events --app "python app.py" --outputs-file ./cdk-exports.json
-        - export MYAPP_MODULE_METADATA=$(python -c "import json; file=open('cdk-exports.json'); print(json.load(file)['myapp-${MYAPP_DEPLOYMENT_NAME}-${MYAPP_MODULE_NAME}']['metadata'])")
-destroy:
-  phases:
-    install:
-      commands:
-      - npm install -g aws-cdk@2.20.0
-      - pip install -r requirements.txt
-    build:
-      commands:
-      - cdk destroy --all --force --app "python app.py"
-build_type: BUILD_GENERAL1_LARGE
-```
-
-In the above example, a different CDKv2 version is being installed as an example, AWS CLI commands are issued, and the actual deployment script (AWS CDK) is executed with the output of the CDK being written to SSM as a JSON document so other modules can leverage it.
\ No newline at end of file
diff --git a/docs/source/faq.md b/docs/source/faq.md
index 45eafdb3..50f5ff01 100644
--- a/docs/source/faq.md
+++ b/docs/source/faq.md
@@ -1,4 +1,4 @@
 ## Frequently Asked Questions
 
 ### How do SeedFarmer and AWS CodeSeeder work together?
-`SeedFarmer` is the CLI support for the opensource library [AWS CodeSeeder](https://aws-codeseeder.readthedocs.io/en/latest/) with [source code](https://github.com/awslabs/aws-codeseeder) here.  You can programatically use `AWS CodeSeeder` without `SeedFarmer`, but `SeedFarmer` cannot function without `AWS CodeSeeder`.
+`seedfarmer`is the CLI support for the opensource library [AWS CodeSeeder](https://aws-codeseeder.readthedocs.io/en/latest/) with [source code](https://github.com/awslabs/aws-codeseeder) here.  You can programatically use `AWS CodeSeeder` without `seedfarmer`, but `seedfarmer` cannot function without `AWS CodeSeeder`.
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 03698508..3a6a4fef 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -9,36 +9,23 @@ It has a CommandLine Interface (CLI) based in Python.
 **Seed-Farmer** leverages modular code deployments (`see modules <module_development.html>`_) leveraging `manifests <manifests.html>`_ and `deployspecs <deployspec.html>`_, keeping track of changes and applying changes as need / detected.
 
 
-Architecture
-==============
-**Seed-Farmer** does not create its own deployments, rather it helps to deploy YOUR modules by acting as the broker between your 
-module code and the AWS Cloud via AWS CodeSeeder.
-
-
-.. image:: _static/SeedFarmer.png
-   :alt: Seed-Farmer
-
-#.  Invoke **seedfarmer** CLI
-#.  **seedfarmer** reads/writes deployment metadata with AWS Systems Manager
-#.  **seedfarmer** invokes AWS IAM to create module-specific roles, attaching the proper least-privilege policies
-#.  **seedfarmer** leverages **AWS CodeSeeder** for remote deployment on AWS CodeBuild
-#.  **AWS CodeSeeder** prepares AWS CodeBuild 
-#.  AWS CodeBuild via **AWS CodeSeeder** inspects and fetches data from AWS SecretsManager (if necessary)
-#.  AWS CodeBuild via **AWS CodeSeeder** executes the custom **deployspec** for the module
-#.  AWS CodeBuild via **AWS CodeSeeder** updates AWS Systems Manager with completed module metadata
-#.  **seedfarmer** updates deployment metadata in AWS Systems Manager
-
-
-=======================================
 
 .. toctree::
    :maxdepth: 3
    :caption: Contents:
    
-   reference
-
-
-   usage
+   overview
+   architecture
+   installation
+   bootstrapping
+   project_development
+   manifests
+   cli_commands
+   module_development
+   deployment_guide
+   module_best_practices
+   cookiecutter
+   faq
 
 Indices and tables
 ==================
diff --git a/docs/source/installation.md b/docs/source/installation.md
new file mode 100644
index 00000000..1e3f7632
--- /dev/null
+++ b/docs/source/installation.md
@@ -0,0 +1,12 @@
+# Installation
+
+Seed-Farmer can be installed from PyPi.  It is suggested to create a virtual environment in Python to isolate / separate your libraries.
+
+Create a new directory and create a new virtual env under that dir.
+```bash
+python -m venv .venv
+source .venv/bin/activate
+```
+Then install seedfarmer:
+```bash
+pip install seed-farmer
\ No newline at end of file
diff --git a/docs/source/manifests.md b/docs/source/manifests.md
index a6bc7209..cca79cac 100644
--- a/docs/source/manifests.md
+++ b/docs/source/manifests.md
@@ -1,110 +1,117 @@
-## Manifests
+# Manifests
 
 The CLI is a module-centric deployment manager that follows the GitOps paradigm of code deployment.  Several manifests are required for deployment as defined below.
 
 (deployment_manifest)=
-### Deployment Manifest
-The deployment manifest is the top level manifest and resides in the *modules* directory. There can be only one.
-Its elements are:
-
-- *name*
-  - _REQUIRED_
-  - the name of the project - must be unique per AWS region
-- *groups*
-  - _REQUIRED_
-  - a logical grouping of modules in a YAML list
-  - the ordering of each group is maintained and each group is deployed sequentially
-  - has two required nested elements per entry:
-    - _name_ - a unique group name
-    - _path_ - relative path to module manifest
-- *projectPolicy*
-  - _OPTIONAL_
-  - a global IAM policy for all modules to use in addition to the *modulestack.yaml policy*.  One is provided by default at [resources/projectpolicy.yaml](../resources/projectpolicy.yaml) with the minimum necessary permissions for the CLI to function, but a custom policy can be used.  If a custom policy is provided, it MUST have the minimum access capabilities as found in the provided policy as the default policy is ignored (if *projectPolicy* is populated).
-- *dockerCredentialsSecret*
-  - _OPTIONAL_
-  -  a global name of a SecretsManager parameter that is use by AWS CodeSeeder when building images to prevent throttling from publically hosted image repositories such as DockerHub
-  - the data in the SecretsManager is a json element of the following format:
-  ```json
-  {
-    "docker.io": {
-      "username": "username",
-      "password": "thepassword"
-    }
-  }
-  ```
-- *permissionBoundaryArn*
-  - _OPTIONAL_
-  - The value should be the Name of the Permission Boundary Managed Policy
-  - Below is the command to deploy a sample [Permission Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) Managed Policy stack located in the project, however we expect the consumer of the framework to create a permission boundary as per their security requirements/guidelines and provide the ARN to the key `permissionBoundaryArn`:
-  ```sh
-  aws cloudformation deploy \
-    --template-file ./resources/sample-permissionboundary.yaml \
-    --stack-name permission-boundary-stack \
-    --parameter-overrides ManagedPolicyName=MyAppPermissionBoundary \
-    --capabilities CAPABILITY_NAMED_IAM
-  ```
 
-Here is a sample DeploymentManifest:
+## Deployment Manifest
+The deployment manifest is the top level manifest and resides in the `modules` directory.  Below is an example deployment manifest. 
 
 ```yaml
-name: dev
+name: examples
+toolchainRegion: us-west-2
 groups:
   - name: optionals
-    path: manifests/example-dev/optional-modules.yaml
-  - name: core
-    path: manifests/example-dev/core-modules.yaml
-projectPolicy: resources/mycustomiampolicy.yaml
-dockerCredentialsSecret: aws-myapp-docker-credentials
-permissionBoundaryArn: arn:aws:iam::XXXXXXXXXXXX:policy/MyAppPermissionBoundary
-```
-(module_manifest)=
-### Module Manifest
-
-The module manifest resides alongside the deployment manifest and defines the information the CLI needs to deploy a module or a group of modules - as defined by the group. There can be as many as there are groups.   Each entry in the module manifest is deployed in parallel and ordering is not preserved.
-Its elements are:
-- *name*
-  - _REQUIRED_
-  - the name of the group - must be unique per deployment
-- *path*
-  - _REQUIRED_
-  - relative path to the modules direectory where the code resides for this module
-- *parameters*
-  - _OPTIONAL_
-  - This is a list of  parameters made available to AWS CodeSeeder for the module to deploy.  Please see [parameters](parameters.md) for more details
-
-Here is a sample ModuleManifest:
+    path: manifests-multi/examples/optional-modules.yaml
+  - name: optionals-2
+    path: manifests-multi/examples/optional-modules-2.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId:
+      valueFrom:
+        envVariable: PRIMARY_ACCOUNT
+    default: true
+    parametersGlobal:
+      dockerCredentialsSecret: nameofsecret
+      permissionsBoundaryName: policyname
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          dockerCredentialsSecret: nameofsecret
+          permissionsBoundaryName: policyname
+      - region: us-east-2
+  - alias: secondary
+    accountId: 123456789012
+    regionMappings:
+      - region: us-west-2
+        parametersRegional:
+          dockerCredentialsSecret: nameofsecret
+          permissionsBoundaryName: policyname
+      - region: us-east-2
+        default: true
 
-```yaml
-name: metadata-storage
-path: modules/core/metadata-storage/
-parameters:
-  - name: glue-db-suffix
-    value: vsidata
-  - name: rosbag-bagfile-table-suffix
-    value: Rosbag-BagFile-Metadata
-  - name: rosbag-scene-table-suffix
-    value: Rosbag-Scene-Metadata
 ```
 
-Using the example from Deployment Manifest above, there would be two (2) Module Manifests named core and optionals.  Here would be their corresponding examples:<br>
-_optional-modules.yaml_
+- **name** : this is the name of your deployment.  There can be only one deployment with this name in a project.
+- **toolchainRegion** :the designated region that the `toolchain` is created in
+- **groups** : the relative path to the [`module manifests`](module_manifest) that define each module in the group.  This sequential order is preserved in deployment, and reversed in destroy.
+  - **name** - the name of the group
+  - **path**- the relative path to the [module manifest](module_manifest)
+- **targetAccountMappings** - section defining target accounts and configurations, this is a list
+  - **alias** - the logical name for an account, referenced by [`module manifests`](module_manifest)
+  - **account** - the account id tied to the alias.  This parameter also supports [Environment Variables](envVariable)
+  - **default** - this designates this mapping as the default account for all modules unless otherwise specified.  This is primarily for supporting migrating from `seedfarmer v1` to the current version.
+  - **parametersGlobal** - these are parameters that apply to all region mappings unless otherwise overridden at the region level
+    - **dockerCredentialsSecret** - please see [Docker Credentials Secret](dockerCredentialsSecret)
+    - **permissionsBoundaryName** - the name of the [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) policy to apply to all module-specific roles created
+  - **regionMappings** - section to define region-specific configurations for the defined account, this is a list
+    - **region** - the region name
+    - **default** - this designates this mapping as the default region for all modules unless otherwise specified.  This is primarily for supporting migrating
+    - **parametersRegional** - these are parameters that apply to all region mappings unless otherwise overridden at the region level
+      - **dockerCredentialsSecret** - please see [Docker Credentials Secret](dockerCredentialsSecret)
+      - **permissionsBoundaryName** - the name of the [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) policy to apply to all module-specific roles created
 
+
+(module_manifest)=
+## Module Manifest
+
+The module manifest is referred to by the [deployment manifest](deployment_manifest) and defines the information the CLI needs to deploy a module or a group of modules - as defined by the group.  Each entry in the module manifest is deployed in parallel and ordering is not preserved.
 ```yaml
 name: networking
 path: modules/optionals/networking/
+targetAccount: primary
 parameters:
   - name: internet-accessible
     value: true
 ---
-name: datalake-buckets
-path: modules/optionals/datalake-buckets
+name: buckets
+path: modules/optionals/buckets
+targetAccount: secondary
+targetRegion: us-west-2
 parameters:
   - name: encryption-type
     value: SSE
+  - name: some-name
+    valueFrom:
+      moduleMetadata:
+        group: optionals
+        name: networking
+        key: VpcId
 ```
+- **name** - the name of the group
+- **path** - the relative path to the module code in the project
+- **targetAccount** - the alias of the account from the [deployment manifest mappings](deployment_manifest)
+- **targetRegion** - the name of the region to deploy to - this overrides any mappings 
+- **parameters** - the parameters section .... see [Parameters](parameters)
+
+(parameters)=
+## Parameters
+
+Parameters are defined in the [Module Manifests](module_manifest) as Key/Value pairs.  On deployment, values are serialized to JSON and passed to the module’s CodeBuild execution as Environment Variables. 
+
+Modules should be parameterized to promote extensibility.  The CLI and [Module Manifests](module_manifest) support parameters of multiple types:
+- [User Defined](user_defined) - a simple key/value string
+- [Module Metadata](modulemetadata) from other deployed modules 
+- [Environment Variables](envVariable)
+- [AWS SSM Parameter](ssm_parameter)
+- [AWS Secrets Manager](secrets_manager)
 
-_core-modules.yaml_
+They are defined in the module manifest for each module.
 
+(user_defined)=
+### User-Defined
+These are simple key/value pairs passed in as strings.  Here is an example of a module with user-defined parameters:
 ```yaml
 name: metadata-storage
 path: modules/core/metadata-storage/
@@ -115,20 +122,42 @@ parameters:
     value: Rosbag-BagFile-Metadata
   - name: rosbag-scene-table-suffix
     value: Rosbag-Scene-Metadata
----
+```
+In this example, the `glue-db-suffix` parameter will be exposed to the CodeBuild via AWS CodeSeeder as an [environment parameter](params_in_codeseeder).
+
+
+
+(envVariable)=
+### Environment Variables
+`seedfarmer` supports using [Dotenv](https://github.com/theskumar/python-dotenv) for dynamic replacement.  Wen a file names `.env` is placed at the projecr root (where `seedfarmer.yaml` resides), any value in a manifest with a key of `envVariable` will be matched and replaced with the corresponding environment variable.
+
+```yaml
+name: opensearch
+path: modules/core/opensearch/
+parameters:
+  - name: vpc-id
+    valueFrom:
+      envVariable: ENV_VPC_ID
+```
+In this example, the `opensearch` module is referencing an environment parameter named `ENV_VPC_ID`. <br>
+
+The `opensearch` module deployment will then have an environment parameter set in the environment to the value of the parameter.  It can then be referenced as an [environment parameter](params_in_codeseeder) in the deployment.
+
+`Environment Variables` also support dynamically changing the `account_id` in manifests to avoid hard-coding an `account_id` in any manifest.
+```yaml
+accountId:
+  valueFrom:
+    envVariable: PRIMARY_ACCOUNT
+```
+<br>
+
+(modulemetadata)=
+### Module Metadata
+Parameters can leverage exported metadata from existing modules.  You will need to know the group name, module name and the name of the parameter.  It leverages the `valueFrom` keyword and has a nested definiton. Below is an example:
+```yaml
 name: opensearch
 path: modules/core/opensearch/
 parameters:
-  - name: opensearch_data_nodes
-    value: 1
-  - name: opensearch_data_nodes_instance_type
-    value: r6g.large.search
-  - name: opensearch_master_nodes
-    value: 0
-  - name: opensearch_master_nodes_instance_type
-    value: r6g.large.search
-  - name: opensearch_ebs_volume_size
-    value: 30
   - name: vpc-id
     valueFrom:
       moduleMetadata:
@@ -136,7 +165,65 @@ parameters:
         name: networking
         key: VpcId
 ```
-### Parameters
-The module manifest leverages parameters, either user-defined or from an already deployed module (`modulemetadata`).
+In this example, the `opensearch` module is referencing a module metadata parameter `VpcId` from the `networking` module in the `optionals` group. <br>
+
+The `opensearch` module deployment will then have an environment parameter set in the environment to the value of the vpc-id that is exported from the `networking` module.  They can then be referenced as an [environment parameter](params_in_codeseeder) in the deployment.
+
+
+(ssm_parameter)=
+### AWS SSM Parameter
+Parameters can leverage key/value pairs stored in [AWS SSM Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html).  You will need to know the name of the parameter.  It leverages the `valueFrom` keyword and has a nested definiton. Below is an example:
+```yaml
+name: opensearch
+path: modules/core/opensearch/
+parameters:
+  - name: vpc-id
+    valueFrom:
+      parameterStore: my-vpc-id
+```
+In this example, the `opensearch` module is referencing an SSM parameter named `my-vpc-id` from AWS SSM Parameter Store. <br>
+
+The `opensearch` module deployment will then have an environment parameter set in the environment to the value of the parameter that is fetched.  It can then be referenced as an [environment parameter](params_in_codeseeder) in the deployment.
+
+(secrets_manager)=
+### AWS SecretsManager
+Parameters can leverage secured secrets in [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html).  You will need to know the name of the secret.  It leverages the `valueFrom` keyword and has a nested definiton. Below is an example:
+```yaml
+name: opensearch
+path: modules/core/opensearch/
+parameters:
+  - name: vpc-id
+    valueFrom:
+      secretsManager: my-secret-vpc-id
+```
+In this example, the `opensearch` module is referencing a secret named `my-secret-vpc-id` from AWS Secrets Manager. <br>
+
+The `opensearch` module deployment will then have an environment parameter set in the environment to the value of the secret that is fetched.  It can then be referenced as an [environment parameter](params_in_codeseeder) in the deployment.  NOTE: the value will be obfusticated in the AWS CodeBuild console in the Environments Section for security purposes.
+
+
+(dockerCredentialsSecret)=
+### Docker Credentials Secret
+A named manifest key pointing to an [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) entry that is use by AWS CodeSeeder when building images to prevent throttling from publically hosted image repositories such as DockerHub. <br>
+<br>
+The data in the SecretsManager is a json element of the following format:
+  ```json
+  {
+    "docker.io": {
+      "username": "username",
+      "password": "thepassword"
+    }
+  }
+  ```
+
+  (params_in_codeseeder)=
+### Parameters in AWS CodeSeeder
+
+CodeBuild Environment Variables that are set via AWS CodeSeeder are made known to the module using a naming convention based off the Parameter’s key. Parameter keys are converted from “PascalCase”, “camelCase”, or “kebab-case” to all upper “SNAKE_CASE” and prefixed with “<<project>>_PARAMETER_”.  If the name of our project is "MY_APP" the resulting environment parameters will be:
 
-Please see [parameters](parameters.md) for more details
+``` 
+* someKey will become environment variable MYAPP_PARAMETER_SOME_KEY
+* SomeKey will become environment variable MYAPP_PARAMETER_SOME_KEY
+* some-key will become environment variable MYAPP_PARAMETER_SOME_KEY
+* some_key will become environment variable MYAPP_PARAMETER_SOME_KEY
+* somekey will become environment variable MYAPP_PARAMETER_SOMEKEY
+```
\ No newline at end of file
diff --git a/docs/source/module_best_practices.md b/docs/source/module_best_practices.md
index 65d29e8e..3ef2ac7d 100644
--- a/docs/source/module_best_practices.md
+++ b/docs/source/module_best_practices.md
@@ -1,30 +1,32 @@
-## Best Practices
+# Best Practices
 
 
-### Ordering of Module Deployment
-The `SeedFarmer CLI` uses the [deployment manifest](manifests.md) group ordering to sequentially deploy groups (modules in a group are deployed in parallel), and this ordering is preserved.  On the destroy of the deployment, the reverse ordering of the groups is followed (still, each module in the group is destroy in parallel).  It is important to identify your module inter-dependancies and allocate them to the proper group in the proer order.  For example, if a compute resource (ex. an ec2 instance) needs to be deployed in a VPC, then the module that creates the VPC should be in a group that is deployed BEFORE the group that contains the EC2 module.  
+## Ordering of Module Deployment
+The `seedfarmer` CLI uses the [deployment manifest](manifests.md) group ordering to sequentially deploy groups (modules in a group are deployed in parallel), and this ordering is preserved.  On the destroy of the deployment, the reverse ordering of the groups is followed (still, each module in the group is destroy in parallel).  It is important to identify your module inter-dependancies and allocate them to the proper group in the proer order.  For example, if a compute resource (ex. an ec2 instance) needs to be deployed in a VPC, then the module that creates the VPC should be in a group that is deployed BEFORE the group that contains the EC2 module.  
 
-The `SeedFarmer CLI` does not currently support inter-module dependency management, so the best-pracice guideline is to group modules that are independent of one-another and order the groups based on module-dependancy.
+The `seedfarmer` CLI does not currently support inter-module dependency management, so the best-pracice guideline is to group modules that are independent of one-another and order the groups based on module-dependancy.
 
 
 ***
-### Preferred Build Tooling
+## Preferred Build Tooling
 The [deployspec](deployspec.md) supports using any executable library.  The preferred library is the [AWS CDKv2](https://docs.aws.amazon.com/cdk/v2/guide/home.html).  This version of the CDK allows for isolation of access to AWS Services for [AWS CodeBuld](https://docs.aws.amazon.com/codebuild/latest/userguide/welcome.html) via [AWS CodeSeeder](https://aws-codeseeder.readthedocs.io/en/latest/) to only the CodeBuild service in the context of deploying your code.  But, the deployspec also has the ability to execute commands that are outside of the CDK constructs.  This provides the least-privelege access to AWS resources without needing to give additional permissions 
 
 ***
-### When to use a `modulestack.yaml` policy
+## When to use a `modulestack.yaml` policy
 Whenever the [deployspec](deployspec.md) is executing commands directly (ex. `aws s3 cp`), the role AWS CodeSeeder uses to exeucte the build needs to have access to those resources. This is done via the [modulestack.yaml](modulestack.md) policy.  Follow the least-priveleges guidelines for granting access within this policy.
 
 ***
-### Validation of Module Code
+## Validation of Module Code
 We have available two (2) scripts that can format and check your moduel code:
 * `scripts/fix.sh` - this will correct the format of your code or tell you what the issues are
 * `scripts/validate.sh` - this will perform checks of your code for consistency
 
+These are found in the [Seed-Farmer Git Repo](https://github.com/awslabs/seed-farmer)
+
 You should run these against your module codebase prior to making code repository commits.
 Feel free to copy these scripts into your own project for application to your modules
 
-#### fix.sh
+### fix.sh
 The `fix.sh` script will apply standardization libraries to your code based on the language.
 * `python` code uses [isort](https://pycqa.github.io/isort/) and [black](https://black.readthedocs.io/en/stable/)
 * `typescript` code uses [prettier](https://www.npmjs.com/package/prettier)
@@ -36,7 +38,7 @@ To run, from commandline pass in the language and relative path to yur module:
 ./scripts/fix.sh --language typescript --path <relative path to code>
 ```
 
-#### validate.sh
+### validate.sh
 The `validate.sh` script will apply check libraries to your code based on the language.
 * `python` code uses [isort](https://pycqa.github.io/isort/), [black](https://black.readthedocs.io/en/stable/), [mypy](https://mypy.readthedocs.io/en/stable/), [flake8](https://flake8.pycqa.org/en/latest/)
 * `typescript` code uses [prettier](https://www.npmjs.com/package/prettier)
diff --git a/docs/source/module_development.md b/docs/source/module_development.md
index 8118c6a7..a47c5c84 100644
--- a/docs/source/module_development.md
+++ b/docs/source/module_development.md
@@ -1,20 +1,21 @@
-##  Creating a New Module
+# Module Development
 
 To create a new module, there are certain requirements needed to allow the CLI (and AWS CodeSeedeer) to deploy your code.
-### Required Files
+## Required Files
 Every module must have the following:
 - [deployment manifest](deployment_manifest)
 - [module manifest](module_manifest)
-- [deployspec.yaml](deployspec.md)
-- [README.md of module](module_readme.md)
+- [deployspec.yaml](deployspec)
+- [README.md of module](module_readme)
 
-### Optional Files
-- [modulestack.yaml](modulestack.md)
+## Optional Files
+- [modulestack.yaml](modulestack)
+
+
+## Create a new module
 
 
-### Create a new module
 
-#### Create the Module Skeleton
 The [CLI](cli_commands.md) provides an `init` method to create your skeleton module code.  We will create a new module named `mymodule` in the group `mygroup`
 ```
 > seedfarmer init module -g mygroup -m mymodule
@@ -22,12 +23,214 @@ The [CLI](cli_commands.md) provides an `init` method to create your skeleton mod
 ```
 The strucuture for your module is in place.  Edit the `deploysepc.yaml` as needed.  We provde a `modulestack.template` file that can be edited for additional permissions, and that file needs to be renamed to `modulestack.yaml` in order to be used.  
 
-Please refer to our [initalization documetation](cookiecutter.md) for more details
+For a deep-dive on the module creation command, see [HERE](indepth_module_creation).
+
+(deployspec)=
+## Deployspec
+
+Each Module must contain a `deployspec.yaml` file. This file defines deployment instructions read by seedfarmer. These instructions include the external module metadata required, libraries/utilities to be installed, and deployment commands. The deployspec.yaml is very similar to the AWS CodeBuild [buildspec.yaml](https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html) implementing the phases structure and adding a module_dependencies section for declaring other modules whose metadata should be made to the module on deployment.
+
+### Structure
+Below is a sample manifest that just 'echo' data to the environment runtime:
+
+```yaml
+deploy:
+  phases:
+    install:
+      commands:
+      - pip install -r requirements.txt
+    pre_build:
+      commands:
+      - echo "Prebuild stage"
+    build:
+      commands:
+      - echo "bash deploy.sh"
+    post_build:
+      commands:
+      - echo "Deploy successful"
+destroy:
+  phases:
+    install:
+      commands:
+      - pip install -r requirements.txt
+    pre_build:
+      commands:
+      - echo "Prebuild stage"
+      - echo "testing change"
+    build:
+      commands:
+      - echo "DESTROY!"
+    post_build:
+      commands:
+      - echo "Destroy successful"
+build_type: BUILD_GENERAL1_LARGE
+```
+
+The deployspec is broken into 2 major areas of focus: `deploy `and `destroy`.  Each of these areas have 4 distinct phases in which commands can be executed (ex. installing supporting libraries, setting environment variables, etc.)  It is in these sections that AWS CodeSeeder makes calls to deploy/destroy on the modules' behalf.  The example below will highlight.
+
+The parameter `build_type` allows module developers to choose the size of the compute instance AWS CodeSeeder will leverage as defined [HERE](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html).  This parameter is defaulted to `BUILD_GENERAL1_SMALL`
+
+The currently supported values are:
+```
+- BUILD_GENERAL1_SMALL
+- BUILD_GENERAL1_MEDIUM
+- BUILD_GENERAL1_LARGE 
+- BUILD_GENERAL1_2XLARGE
+```
+
+
+#### Example
+The following is an example deployspec that issues a series of commands.  This is only an example...
+
+```yaml
+deploy:
+  phases:
+    install:
+      commands:
+        - npm install -g aws-cdk@2.20.0
+        - apt-get install jq
+        - pip install -r requirements.txt
+    build:
+      commands:
+        - aws iam create-service-linked-role --aws-service-name elasticmapreduce.amazonaws.com || true
+        - export ECR_REPO_NAME=$(echo $MYAPP_PARAMETER_FARGATE | jq -r '."ecr-repository-name"')
+        - aws ecr describe-repositories --repository-names ${ECR_REPO_NAME} || aws ecr create-repository --repository-name ${ECR_REPO_NAME}
+        - export IMAGE_NAME=$(echo $MYAPP_PARAMETER_FARGATE | jq -r '."image-name"')
+        - export COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
+        - export IMAGE_TAG=${COMMIT_HASH:=latest}
+        - export REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPO_NAME
+        - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
+        - >
+          echo "MYAPP_PARAMETER_SHARED_BUCKET_NAME: ${MYAPP_PARAMETER_SHARED_BUCKET_NAME}"
+        - echo Building the Docker image...          
+        - cd service/ && docker build -t $REPOSITORY_URI:latest .
+        - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
+        - docker push $REPOSITORY_URI:latest && docker push $REPOSITORY_URI:$IMAGE_TAG
+        - cd .. && cdk deploy --all --require-approval never --progress events --app "python app.py" --outputs-file ./cdk-exports.json
+        - export MYAPP_MODULE_METADATA=$(python -c "import json; file=open('cdk-exports.json'); print(json.load(file)['myapp-${MYAPP_DEPLOYMENT_NAME}-${MYAPP_MODULE_NAME}']['metadata'])")
+destroy:
+  phases:
+    install:
+      commands:
+      - npm install -g aws-cdk@2.20.0
+      - pip install -r requirements.txt
+    build:
+      commands:
+      - cdk destroy --all --force --app "python app.py"
+build_type: BUILD_GENERAL1_LARGE
+```
+
+In the above example, a different CDKv2 version is being installed as an example, AWS CLI commands are issued, and the actual deployment script (AWS CDK) is executed with the output of the CDK being written to SSM as a JSON document so other modules can leverage it.
+
+(module_readme)=
+## Module ReadMe
+
+As part of the process to promote reusability and sharabiltiy of the modules, each module is required to have a README.md that talks directly to end users and describes:
+
+- the description of the module
+- the inputs - parameter names
+  - required
+  - optional
+- the outputs - the parameter names in JSON format
+  - having a sample output is highly recommneded so other users cancan quickly reference in their modules
+
+
+### Example 
+
+Below is a sample of the sections in a README.md for the modules:
+
+```
+# OpenSearch Module
+
+
+## Description
+
+This module creates an OpenSearch cluster
+
+
+## Inputs/Outputs
+
+### Input Paramenters
+
+#### Required
+
+- `vpc-id`: The VPC-ID that the cluster will be created in
+
+#### Optional
+- `opensearch_data_nodes`: The number of data nodes, defaults to `1`
+- `opensearch_data_nodes_instance_type`: The data node type, defaults to `r6g.large.search`
+- `opensearch_master_nodes`: The number of master nodes, defaults to `0`
+- `opensearch_master_nodes_instance_type`: The master node type, defaults to `r6g.large.search`
+- `opensearch_ebs_volume_size`: The EBS volume size (in GB), defaults to `10`
+
+### Module Metadata Outputs
+
+- `OpenSearchDomainEndpoint`: the endpoint name of the OpenSearch Domain
+  `OpenSearchDomainName`: the name of the OpenSearch Domain
+- `OpenSeearchDashboardUrl`: URL of the OpenSearch cluster dashboard
+- `OpenSearchSecurityGroupId`: name of the DDB table created for Rosbag Scene Data
+
+#### Output Example
+
+```json
+{
+  "OpenSearchDashboardUrl": "https://vpc-myapp-test-core-opensearch-aaa.us-east-1.es.amazonaws.com/_dashboards/",
+  "OpenSearchDomainName": "vpc-myapp-test-core-opensearch-aaa",
+  "OpenSearchDomainEndpoint": "vpc-myapp-test-core-opensearch-aaa.us-east-1.es.amazonaws.com",
+  "OpenSearchSecurityGroupId": "sg-0475c9e7efba05c0d"
+}
+
+```
+
+(modulestack)=
+## ModuleStack
+
+The modulestack (`modulestack.yaml`) is an optional AWS Cloudformation file that contains the granular permissions that AWS Codeseeder will need to deploy your module.  It is recommended to use a least-privelege policy to promote security best practices.
+
+By default, the CLI uses AWS CDKv2, which assumes a role that has the permissions to deploy via CloudFormation and is the recommended practice.  You have the ability to use the `modulestack.yaml` to give additial permissions to `AWS CodeSeeder` on your behalf.  
+
+Typical cases when you would use a  `modulestack.yaml`:
+- any time you are invoking AWS CLI in the deployspec (not in the scope of the CDK) - for example: copying files to S3
+- you perfer to use the AWSCLI v1 - in which a least-privilege policy is necessary for ALL AWS Services.
+
+
+### Initial Template
+
+Below is a sample template that is provoded by the [CLI](cli_commands.md).  The `Parameters` section is populated with the input provided from the CLI when deploying.  
+
+*** It DOES have a policy definiton that is wide open - you SHOULD CHANGE THIS - it is only a template!
+
+```yaml
+AWSTemplateFormatVersion: 2010-09-09
+Description: This template deploys a Module specific IAM permissions
+
+Parameters:
+  DeploymentName:
+    Type: String
+    Description: The name of the deployment
+  ModuleName:
+    Type: String
+    Description: The name of the Module
+  RoleName:
+    Type: String
+    Description: The name of the IAM Role
+
+Resources:
+  Policy:
+    Type: 'AWS::IAM::Policy'
+    Properties:
+      PolicyDocument:
+        Statement:
+          - Effect: Allow
+            Action: '*'
+            Resource: '*'
+        Version: 2012-10-17
+      PolicyName: "myapp-modulespecific-policy"
+      Roles: [!Ref RoleName]
+```
 
-#### Add the Manifests
+## Add the Manifests
 Create a new module manifest (see [manifests](module_manifest)) and place it in the `manifests/` directory, under a logical directory.  If the `deployment.yaml` manifest does not exist, create it also.  Add your `module manifest` to the `deployment manifest`.
 
-#### Start Developing
-Your code is now seedfarmer-enabled for deployment.
 
 
diff --git a/docs/source/module_readme.md b/docs/source/module_readme.md
deleted file mode 100644
index aea265be..00000000
--- a/docs/source/module_readme.md
+++ /dev/null
@@ -1,61 +0,0 @@
-## Module ReadMe Doc
-
-As part of the process to promote reusability and sharabiltiy of the modules, each module is required to have a README.md that talks directly to end users and describes:
-
-- the description of the module
-- the inputs - parameter names
-  - required
-  - optional
-- the outputs - the parameter names in JSON format
-  - having a sample output is highly recommneded so other users cancan quickly reference in their modules
-
-
-### Example 
-
-Below is a sample of the sections in a README.md for the modules:
-
-```
-# OpenSearch Module
-
-
-## Description
-
-This module creates an OpenSearch cluster
-
-
-## Inputs/Outputs
-
-### Input Paramenters
-
-#### Required
-
-- `vpc-id`: The VPC-ID that the cluster will be created in
-
-#### Optional
-- `opensearch_data_nodes`: The number of data nodes, defaults to `1`
-- `opensearch_data_nodes_instance_type`: The data node type, defaults to `r6g.large.search`
-- `opensearch_master_nodes`: The number of master nodes, defaults to `0`
-- `opensearch_master_nodes_instance_type`: The master node type, defaults to `r6g.large.search`
-- `opensearch_ebs_volume_size`: The EBS volume size (in GB), defaults to `10`
-
-### Module Metadata Outputs
-
-- `OpenSearchDomainEndpoint`: the endpoint name of the OpenSearch Domain
-  `OpenSearchDomainName`: the name of the OpenSearch Domain
-- `OpenSeearchDashboardUrl`: URL of the OpenSearch cluster dashboard
-- `OpenSearchSecurityGroupId`: name of the DDB table created for Rosbag Scene Data
-
-#### Output Example
-
-```json
-{
-  "OpenSearchDashboardUrl": "https://vpc-myapp-test-core-opensearch-aaa.us-east-1.es.amazonaws.com/_dashboards/",
-  "OpenSearchDomainName": "vpc-myapp-test-core-opensearch-aaa",
-  "OpenSearchDomainEndpoint": "vpc-myapp-test-core-opensearch-aaa.us-east-1.es.amazonaws.com",
-  "OpenSearchSecurityGroupId": "sg-0475c9e7efba05c0d"
-}
-
-```
-
-
-```
\ No newline at end of file
diff --git a/docs/source/modulestack.md b/docs/source/modulestack.md
deleted file mode 100644
index c9c1d54f..00000000
--- a/docs/source/modulestack.md
+++ /dev/null
@@ -1,45 +0,0 @@
-## ModuleStack
-
-The modulestack (`modulestack.yaml`) is an optional AWS Cloudformation file that contains the granular permissions that AWS Codeseeder will need to deploy your module.  It is recommended to use a least-privelege policy to promote security best practices.
-
-By default, the CLI uses AWS CDKv2, which assumes a role that has the permissions to deploy via CloudFormation and is the recommended practice.  You have the ability to use the `modulestack.yaml` to give additial permissions to `AWS CodeSeeder` on your behalf.  
-
-Typical cases when you would use a  `modulestack.yaml`:
-- any time you are invoking AWS CLI in the deployspec (not in the scope of the CDK) - for example: copying files to S3
-- you perfer to use the AWSCLI v1 - in which a least-privilege policy is necessary for ALL AWS Services.
-
-
-#### Initial Template
-
-Below is a sample template that is provoded by the [CLI](cli_commands.md).  The `Parameters` section is populated with the input provided from the CLI when deploying.  
-
-*** It DOES have a policy definiton that is wide open - you SHOULD CHANGE THIS - it is only a template!
-
-```yaml
-AWSTemplateFormatVersion: 2010-09-09
-Description: This template deploys a Module specific IAM permissions
-
-Parameters:
-  DeploymentName:
-    Type: String
-    Description: The name of the deployment
-  ModuleName:
-    Type: String
-    Description: The name of the Module
-  RoleName:
-    Type: String
-    Description: The name of the IAM Role
-
-Resources:
-  Policy:
-    Type: 'AWS::IAM::Policy'
-    Properties:
-      PolicyDocument:
-        Statement:
-          - Effect: Allow
-            Action: '*'
-            Resource: '*'
-        Version: 2012-10-17
-      PolicyName: "myapp-modulespecific-policy"
-      Roles: [!Ref RoleName]
-```
\ No newline at end of file
diff --git a/docs/source/overview.md b/docs/source/overview.md
new file mode 100644
index 00000000..2a1eff37
--- /dev/null
+++ b/docs/source/overview.md
@@ -0,0 +1,29 @@
+# Overview
+`seedfarmer` is a python-based CICD library that leverages the GitOps paradigm to manage deployed code.  It is tooling-agnostic (can support AWS CDK, CloudFormation, bash scripting, Terraform, etc).  It leverages declarative manifests to define deployable code (`modules`), and manages the state of the deployed code - detecting and applying changes as needed.  
+
+(multiaccount_support)=
+## Multi Account Support
+`seedfarmer` uses the concept of a`toolchain account` and `target account`(s).  These accounts have roles associated with them that have the minimal permissions necessary to perform deployments.  The `toolchain account` role assumes the `target account` role.  `seedfarmer` is configured that ONLY this role assumption can occur.
+
+- `toolchain account` -- the primary account that:
+  - stores the deployment-specific information
+  - has the `toolchain-role` necessary to manage and coordinate `seedfarmer` deployments
+  - is region-centric due to metadata storage
+- `target account` -- the account(s) where:
+  - modules are deployed
+  - has the `deployment-role` necessary to deploy modules
+  - is region-centric due to metadata storage
+
+There can be at any time only ONE (1) `toolchain account` with MANY `target account`s.  And, a `toolchain account` can be also `target account`. Each account only needs to be boostrapped once - regardless of the region that modules are deployed.  The `roles` created are global to the account. 
+
+
+## Concepts
+All `seedfarmer` deployments leverage a logical separation of artifacts.  A single `Project` can have multiple `Deployments` of `Groups` of `Modules`.
+
+`Project` - a project as a direct one-to-one relationship with the [AWS Codeseeder](https://aws-codeseeder.readthedocs.io/en/latest/) managed CodeBuild project.  You can have multiple projects in an account(s) and they are isloated from one another (no one project can use artifact from another project).
+
+`Deployment` - a deployment represents all the modules leveraging AWS resources in one or many accounts.  It is metadata that gives isolation from other deployments in the same project.
+
+`Group` - a group represents all modules that can be deployed concurrently. No module in a group can have a dependency on another module in the same group.  `seedfarmer` keeps track of the ordering of groups for deployment and reverses the ordering of the groups for destruction.
+
+`Module` - a module is what gets deployed.  It is represented by code.  A module can be deployed multiple times in the same `Deployment` as long as it has a unique logical name
diff --git a/docs/source/parameters.md b/docs/source/parameters.md
deleted file mode 100644
index 42751959..00000000
--- a/docs/source/parameters.md
+++ /dev/null
@@ -1,65 +0,0 @@
-## Parameters
-
-Parameters are defined in the [Module Manifests](module_manifest) as Key/Value pairs.  On deployment, values are serialized to JSON and passed to the module’s CodeBuild execution as Environment Variables. 
-
-Modules should be parameterized to promote extensibility.  The CLI and [Module Manifests](module_manifest) support parameters of two types:
-- user defined - a simple key/value string
-- exported metadata from other deployed modules (module metadata)
-
-They are defined in the module manifest for each module.
-
-### User-Defined
-These are simple key/value pairs passed in as strings.  Here is an example of a module with user-defined parameters:
-```yaml
-name: metadata-storage
-path: modules/core/metadata-storage/
-parameters:
-  - name: glue-db-suffix
-    value: vsidata
-  - name: rosbag-bagfile-table-suffix
-    value: Rosbag-BagFile-Metadata
-  - name: rosbag-scene-table-suffix
-    value: Rosbag-Scene-Metadata
-```
-In this example, the `glue-db-suffix` parameter will be exposed to the CodeBuild via AWS CodeSeeder as:
-```MYAPP_PARAMETER_GLUE_DB_SUFFIX=vsidata```.
-
-See <b>Parameters in AWS CodeSeeder</b> below.
-
-### From Module Metadata
-Parameters can also leverage exported metadata from eisting modules.  You will need to know the group name, module name and the name of the paramter.  It leverages the `valueFrom` keyword (instead of `value') and has a nested definiton. Below is an example:
-```yaml
-name: opensearch
-path: modules/core/opensearch/
-parameters:
-  - name: vpc-id
-    valueFrom:
-      moduleMetadata:
-        group: optionals
-        name: networking
-        key: VpcId
-```
-In this example, the `opensearch` module is referencing a module metadata parameter `VpcId` from the `networking` module in the `optionals` group. <br>
-
-The `opensearch` module deployment will then have an environment parameter set: `MYAPP_PARAMETER_VPC_ID` in the environment to the value of the vpc-id that is exported from the `networking` module.  They can then be referenced as an environment parameter in the deployment
-
-####  Parameters in AWS CodeSeeder
-
-Environment Variables are made known to the module using a naming convention based off the Parameter’s key. Parameter keys are converted from “PascalCase”, “camelCase”, or “kebab-case” to all upper “SNAKE_CASE” and prefixed with “MYAPP_PARAMETER_”. Examples:
-
-``` * someKey will become environment variable MYAPP_PARAMETER_SOME_KEY
-* SomeKey will become environment variable MYAPP_PARAMETER_SOME_KEY
-* some-key will become environment variable MYAPP_PARAMETER_SOME_KEY
-* some_key will become environment variable MYAPP_PARAMETER_SOME_KEY
-* somekey will become environment variable MYAPP_PARAMETER_SOMEKEY
-```
-
-
-
-The CLI will populate all parameters defined with a prefix of `MYAPP_PARAMETER_`.  All `name` characters will be converted to upper case and all `-` are converted to `_` for consistency.  The `value` or `valueFrom` fields will be left unchanged. <br>
-
-In the above example, the runtime environment parameter in CodeBuld (AWS CodeSeeder) for the `vpc-id` will be set to `MYAPP_PARAMETER_VPC_ID`.
-
-
-
-
diff --git a/docs/source/project_development.md b/docs/source/project_development.md
new file mode 100644
index 00000000..3606c063
--- /dev/null
+++ b/docs/source/project_development.md
@@ -0,0 +1,75 @@
+# Project Development
+
+Once `seedfarmer` is installed and bootstrapped, it is important to initialize a project structure properly.  The project encapsulates everything `seedfarmer` needs to deploy and manage your project as a deployment.
+
+The project for a `seedfarmer` compliant deployment is as follows:
+
+```
+<project-name>
+- manifests (dir)
+    -- <group>
+- modules (dir)
+    -- <group> (dir)
+        -- <module>(dir)
+        -- <module>(dir)
+        -- <module>(dir)
+    -- <group> (dir)
+        -- <module>(dir)
+        -- <module>(dir)
+        -- <module>(dir)
+- seedfarmer.yaml
+```
+The `modules`, `manifests`, and `resources` directories are at the same level. 
+
+
+It is important to have the ```seedfarmer.yaml``` at the root of your project.  This allows the `seedfarmer` CLI to be executed anywhere within the project subdirectories.  Its content defines the name of the project that is used for deployment and provides a base of reference for all modules:
+```yaml
+project: <your project name>
+description: <your project description>
+```
+
+(project_initalization)=
+## Initialization
+On your local compute resource, pick a directory and add a file named `seedfarmer.yaml` that has the name of your new project.  For example, a new project named `mynewproject` can be seeded via the following:
+```bash
+echo project: mynewproject > seedfarmer.yaml
+```
+
+
+Now you are ready to create the new project.  
+
+```bash
+seedfarmer init project --help
+Usage: seedfarmer init project [OPTIONS]
+
+  Initialize a project. Make sure seedfarmer.yaml is present in the same
+  location you execute this command!!
+
+Options:
+  -t, --template-url TEXT  The template URL. If not specified, the default
+                           template repo is `https://github.com/awslabs/seed-
+                           farmer`
+  --help                   Show this message and exit.
+```
+This example will create a new directory in your current working directory that contains some bare structure to support development with seedfarmer:
+
+```bash
+seedfarmer init project
+```
+Your project is created and configured to use the `seedfarmer` CLI.
+
+`seedfarmer` uses [CookieCutter](cookiecutter.md)for templating.
+If you want to use a different project template, you can override the default template url. For example:
+```
+seedfarmer init project -t https://github.com/briggySmalls/cookiecutter-pypackage
+```
+NOTE: your project template for a new `seedfarmer` project must contain at least the required project sturcture.
+
+
+
+(permissions_boundary)=
+### Permissions Boundary Support
+`seedfarmer` supports the concept of a [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html).  This should already be deployed in your AWS account prior to use.
+
+
+Please see the [deployment manifest](deployment_manifest) definition for details of configuring `seedfarmer` to use your customized resources.
diff --git a/docs/source/project_structure.md b/docs/source/project_structure.md
deleted file mode 100644
index 998642f0..00000000
--- a/docs/source/project_structure.md
+++ /dev/null
@@ -1,38 +0,0 @@
-## Project Structure
-
-The project for a Seed-Farmer compliant deployment is as follows:
-
-```
-<project-name>
-- manifests (dir)
-    -- <group>
-- modules (dir)
-    -- <group> (dir)
-        -- <module>(dir)
-        -- <module>(dir)
-        -- <module>(dir)
-    -- <group> (dir)
-        -- <module>(dir)
-        -- <module>(dir)
-        -- <module>(dir)
-- resources (dir)
-    -- projectpolicy.yaml
-- seedfarmer.yaml
-```
-The `modules`, `manifests`, and `resources` directories are at the same level. 
-
-The `resources` directory and explanation can be viewed in the [resouces documentation](resources.md).
-
-It is important to have the ```seedfarmer.yaml``` at the root of your project.  This allows the `SeedFarmer CLI` to be executed anywhere within the project subdirectories.  Its content defines the name of the project that is used for deployment and provides a base of reference for all modules:
-```yaml
-project: <your project name>
-description: <your project description>
-```
-***
-There is CLI support for new project creation, and a new project can be created via the following:
-```bash
-    seedfarmer init project -n <my-project-name>
-```
-
-Please refer to our [initalization documetation](cookiecutter.md) for more details
-
diff --git a/docs/source/resources.md b/docs/source/resources.md
deleted file mode 100644
index a1c34234..00000000
--- a/docs/source/resources.md
+++ /dev/null
@@ -1,15 +0,0 @@
-## Resources
-
-The resources section/directory of the [project stucture](project_structure.md) contains resources (policies) that can be applied to all the roles created for [AWS CodeSeeder](https://aws-codeseeder.readthedocs.io/en/latest/) to use.
-
-(project_policy)=
-### Project Policy
-`SeedFarmer` expects a policy located at `resources/projectpolicy.yaml`.  This provided by default when using the [project initialization](cookiecutter_new_project).  This can be used as-is, modified, or you can provide your own - but this policy contains the MINIMUM permissions `SeedFarmer` needs to operate.
-
-(permission_boundary)=
-### Permission Boundary Support
-`SeedFarmer` supports the concept of a [permission boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html).  This should already be deployed in your AWS account prior to use.
-
-
-Please see the [deployment manifest](deployment_manifest) definition for details of configuring `SeedFarmer` to use your customized resources.
-
diff --git a/docs/source/usage.md b/docs/source/usage.md
deleted file mode 100644
index ed9754c7..00000000
--- a/docs/source/usage.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Usage
-
-## Install SeedFarmer
-
-Seed-Farmer can be installed from PyPi.  It is suggested to create a virtual environment in Python to isolate / separate your libraries.
-
-Create a new directory and create a new virtual env under that dir.
-```bash
-python -m venv .venv
-source .venv/bin/activate
-```
-Then install seedfarmer:
-```bash
-pip install seed-farmer
-```
-
-***
-## Project Structure
-Once SeedFarmer is installed, it is important to initialize a project structure properly.  The project encapsulates everything SeedFarmer needs to deploy and manage your project as a deployment.
-
-The project for a Seed-Farmer compliant deployment is as follows:
-
-```
-<project-name>
-- manifests (dir)
-    -- <group>
-- modules (dir)
-    -- <group> (dir)
-        -- <module>(dir)
-        -- <module>(dir)
-        -- <module>(dir)
-    ...
-- resources (dir)
-    -- projectpolicy.yaml
-- seedfarmer.yaml
-```
-The `modules`, `manifests`, and `resources` directories are at the same level. 
-
-It is important to have the ```seedfarmer.yaml``` at the root of your project. 
-
-Please see [project structure documentation](project_structure.md) for details and the content of `seedfarmer.yaml`
-* [Create a New Project](cookiecutter_new_project)
-* [Create a New Module](cookiecutter_new_module)
-* [Project Policy Reference](project_policy)
-
-***
-## Module Development
-Once the project is defined, you can start adding modules to your project.  
-
-To create a new module, there are certain requirements needed to allow the CLI (and AWS CodeSeedeer) to deploy your code.
-### Required Files
-Every module must have the following:
-- [deployment manifest](deployment_manifest)
-- [module manifest](module_manifest)
-- [deployspec.yaml](deployspec.md)
-- [README.md of module](module_readme.md)
-
-### Optional Files
-- [modulestack.yaml](modulestack.md)
-
-
-### Create a new module
-
-#### Create the Module Skeleton
-The [CLI](cli_commands.md) provides an `init` method to create your skeleton module code.  We will create a new module named `mymodule` in the group `mygroup`
-```
-> seedfarmer init module -g mygroup -m mymodule
-> cd modules/mygroup/mymodule
-```
-The strucuture for your module is in place.  Edit the `deploysepc.yaml` as needed.  We provde a `modulestack.template` file that can be edited for additional permissions, and that file needs to be renamed to `modulestack.yaml` in order to be used.  
-
-Please refer to our [initalization documetation](cookiecutter.md) for more details
-
-#### Add the Manifests
-Create a new module manifest (see [manifests](module_manifest)) and place it in the `manifests/` directory, under a logical directory.  If the `deployment.yaml` manifest does not exist, create it also.  Add your `module manifest` to the `deployment manifest`.
-
-#### Start Developing
-Your code is now seedfarmer-enabled for deployment.
-
-## Module Development Best Practices Guide
-We try to standardize module creation for re-usabilty and consistency.  Please see the [Best Practices Guide](module_best_practices.md) when creating new modules.
-
-***
-## Manifests
-All deployments are driven by manifests.  Please see [manifests](manifests.md) for details.
-- [deployment manifest](deployment_manifest)
-- [module manifest](module_manifest)
-
-***
-## Parameters
-The manifests support passing custom data as parameters to each module (via the [manifests](module_manifest)), and that data can also be sourced from already deployed modules.  
-</br>Please see [parameters](parameters.md) for detailed information.
-
-***
-## CLI
-The CLI is the primary way to interact with your deployments in a project.  Please take the time to execute some [commands](cli_commands.md) to get a feel for them!
-
-***
-## FAQ
-Please see the [FAQ section](faq.md)
\ No newline at end of file
diff --git a/examples/exampleproject/manifests-multi/examples/deployment.yaml b/examples/exampleproject/manifests-multi/examples/deployment.yaml
new file mode 100644
index 00000000..0fc7a17a
--- /dev/null
+++ b/examples/exampleproject/manifests-multi/examples/deployment.yaml
@@ -0,0 +1,26 @@
+name: examples
+toolchainRegion: us-west-2
+groups:
+  - name: optionals
+    path: manifests-multi/examples/optional-modules.yaml
+  - name: optionals-2
+    path: manifests-multi/examples/optional-modules-2.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId:
+      valueFrom:
+        envVariable: PRIMARY_ACCOUNT
+    default: true
+    regionMappings:
+      - region: us-west-2
+        default: true
+      - region: us-east-2
+  - alias: secondary
+    accountId:
+      valueFrom:
+        envVariable: SECONDARY_ACCOUNT
+    default: false
+    regionMappings:
+      - region: us-west-2
+      - region: us-east-2
+        default: true
diff --git a/examples/exampleproject/manifests-multi/examples/optional-modules-2.yaml b/examples/exampleproject/manifests-multi/examples/optional-modules-2.yaml
new file mode 100644
index 00000000..cecf4db3
--- /dev/null
+++ b/examples/exampleproject/manifests-multi/examples/optional-modules-2.yaml
@@ -0,0 +1,20 @@
+name: networking
+path: modules/optionals/networking/
+targetAccount: primary
+parameters:
+  - name: internet-accessible
+    value: true
+---
+name: buckets
+path: modules/optionals/buckets
+targetAccount: secondary
+targetRegion: us-west-2
+parameters:
+  - name: encryption-type
+    value: SSE
+  - name: some-name
+    valueFrom:
+      moduleMetadata:
+        group: optionals
+        name: networking
+        key: VpcId
\ No newline at end of file
diff --git a/examples/exampleproject/manifests-multi/examples/optional-modules.yaml b/examples/exampleproject/manifests-multi/examples/optional-modules.yaml
new file mode 100644
index 00000000..6b3d858e
--- /dev/null
+++ b/examples/exampleproject/manifests-multi/examples/optional-modules.yaml
@@ -0,0 +1,16 @@
+name: networking
+path: modules/optionals/networking/
+targetAccount: secondary
+parameters:
+  - name: internet-accessible
+    value: true
+---
+name: buckets
+path: modules/optionals/buckets
+targetAccount: primary
+targetRegion: us-east-2
+parameters:
+  - name: encryption-type
+    value: SSE
+  - name: some-name
+    value: other
diff --git a/examples/exampleproject/manifests/examples/deployment-upgrade.yaml b/examples/exampleproject/manifests/examples/deployment-upgrade.yaml
new file mode 100644
index 00000000..bae114b7
--- /dev/null
+++ b/examples/exampleproject/manifests/examples/deployment-upgrade.yaml
@@ -0,0 +1,15 @@
+name: examples
+toolchainRegion: us-west-2
+groups:
+  - name: optionals
+    path: manifests/examples/optional-modules.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId:
+      valueFrom:
+        envVariable: PRIMARY_ACCOUNT
+    default: true
+    regionMappings:
+      - region: us-west-2
+        default: true
+      - region: us-east-2
diff --git a/examples/exampleproject/modules/optionals/buckets/stack.py b/examples/exampleproject/modules/optionals/buckets/stack.py
index 6007b1d4..7be2f6be 100755
--- a/examples/exampleproject/modules/optionals/buckets/stack.py
+++ b/examples/exampleproject/modules/optionals/buckets/stack.py
@@ -12,9 +12,9 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
+import hashlib
 import logging
 from typing import Any, cast
-import hashlib
 
 import aws_cdk
 import aws_cdk.aws_iam as aws_iam
@@ -48,9 +48,9 @@ def __init__(
         super().__init__(scope, id, **kwargs)
         Tags.of(scope=cast(IConstruct, self)).add(key="Deployment", value=f"{project_name}-{deployment_name}")
 
-        artifact_bucket_name =f"{project_name}-{deployment_name}-artifacts-bucket-{hash}"
-        unique_ab=(hashlib.sha1(module_name.encode("UTF-8")).hexdigest())[:(60-len(artifact_bucket_name))]
-        
+        artifact_bucket_name = f"{project_name}-{deployment_name}-artifacts-bucket-{hash}"
+        unique_ab = (hashlib.sha1(module_name.encode("UTF-8")).hexdigest())[: (60 - len(artifact_bucket_name))]
+
         artifacts_bucket = aws_s3.Bucket(
             self,
             id="artifacts-bucket",
@@ -67,8 +67,8 @@ def __init__(
             enforce_ssl=True,
         )
 
-        log_bucket_name =f"{project_name}-{deployment_name}-logs-bucket-{hash}"
-        unique_log=(hashlib.sha1(module_name.encode("UTF-8")).hexdigest())[:(60-len(log_bucket_name))]        
+        log_bucket_name = f"{project_name}-{deployment_name}-logs-bucket-{hash}"
+        unique_log = (hashlib.sha1(module_name.encode("UTF-8")).hexdigest())[: (60 - len(log_bucket_name))]
 
         logs_bucket = aws_s3.Bucket(
             self,
diff --git a/examples/exampleproject/modules/optionals/networking/deployspec.yaml b/examples/exampleproject/modules/optionals/networking/deployspec.yaml
index 00409214..2749ab14 100644
--- a/examples/exampleproject/modules/optionals/networking/deployspec.yaml
+++ b/examples/exampleproject/modules/optionals/networking/deployspec.yaml
@@ -2,7 +2,7 @@ deploy:
   phases:
     install:
       commands:
-      # Install whatever additional build libraries 
+      # Install whatever additional build libraries
       - npm install -g aws-cdk@2.20.0
       - pip install -r requirements.txt
     build:
@@ -17,7 +17,7 @@ destroy:
   phases:
     install:
       commands:
-      # Install whatever additional build libraries 
+      # Install whatever additional build libraries
       - npm install -g aws-cdk@2.20.0
       - pip install -r requirements.txt
     build:
diff --git a/seedfarmer/__init__.py b/seedfarmer/__init__.py
index dd518ab2..b99acfd4 100644
--- a/seedfarmer/__init__.py
+++ b/seedfarmer/__init__.py
@@ -16,6 +16,7 @@
 import logging
 import os
 import pathlib
+from typing import Any, Dict, Optional, cast
 
 import pkg_resources
 import yaml
@@ -29,7 +30,9 @@
 __version__: str = pkg_resources.get_distribution(__title__).version
 
 DEBUG_LOGGING_FORMAT = "[%(asctime)s][%(filename)-13s:%(lineno)3d] %(message)s"
-INFO_LOGGING_FORMAT = "[%(filename)-13s:%(lineno)3d] %(message)s"
+INFO_LOGGING_FORMAT = "[%(asctime)s | %(levelname)s | %(filename)-13s:%(lineno)3d | %(threadName)s ] %(message)s"
+
+CLI_ROOT = os.path.dirname(os.path.abspath(__file__))
 
 
 def enable_debug(format: str) -> None:
@@ -52,36 +55,56 @@ def enable_info(format: str) -> None:
 
 enable_info(INFO_LOGGING_FORMAT)
 
-# Locate the config for seedfarmer
-CONFIG_FILE = "seedfarmer.yaml"
-OPS_ROOT = os.getcwd()
-count = 0
-while not os.path.exists(os.path.join(OPS_ROOT, CONFIG_FILE)):
-    if count >= 4:
-        _logger.error("The seedfarmer.yaml was not found at the root of your project.  Please set it and rerun.")
-        exit(0)
-    else:
-        OPS_ROOT = pathlib.Path(OPS_ROOT).parent  # type: ignore
-        count += 1
-
-with open(os.path.join(OPS_ROOT, CONFIG_FILE), "r") as file:
-    config_data = yaml.safe_load(file)
-
-PROJECT = config_data["project"]
-DESCRIPTION = config_data["description"] if "description" in config_data else "NEW PROJECT"
-
-
-@codeseeder.configure(PROJECT.lower(), deploy_if_not_exists=True)
-def configure(configuration: CodeSeederConfig) -> None:
-    LOGGER.debug(f"OPS ROOT (OPS_ROOT) is {OPS_ROOT}")
-    configuration.timeout = 120
-    configuration.codebuild_image = "public.ecr.aws/v3o4w1g6/aws-codeseeder/code-build-base:2.2.0"
-    configuration.pre_build_commands = [
-        (
-            "nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock"
-            " --host=tcp://127.0.0.1:2375 --storage-driver=overlay2 &"
-        ),
-        'timeout 15 sh -c "until docker info; do echo .; sleep 1; done"',
-    ]
-    configuration.python_modules = [f"seed-farmer=={__version__}"]
-    configuration.runtime_versions = {"nodejs": "14", "python": "3.9"}
+
+class Config(object):
+    CONFIG_FILE = "seedfarmer.yaml"
+    OPS_ROOT = os.getcwd()
+    _PROJECT: Optional[str] = None
+    _DESCRIPTION: Optional[str] = None
+
+    def _load_config_data(self) -> None:
+        count = 0
+        while not os.path.exists(os.path.join(self.OPS_ROOT, self.CONFIG_FILE)):
+            if count >= 4:
+                _logger.error("The seedfarmer.yaml was not found at the root of your project. Please set it and rerun.")
+                raise FileNotFoundError(
+                    "The seedfarmer.yaml was not found at the root of your project. Please set it and rerun."
+                )
+            else:
+                self.OPS_ROOT = pathlib.Path(self.OPS_ROOT).parent  # type: ignore
+                count += 1
+
+        with open(os.path.join(self.OPS_ROOT, self.CONFIG_FILE), "r") as file:
+            config_data: Dict[str, Any] = yaml.safe_load(file)
+            self._PROJECT = config_data["project"]
+            self._DESCRIPTION = config_data["description"] if "description" in config_data else "NEW PROJECT"
+
+        @codeseeder.configure(cast(str, self._PROJECT).lower(), deploy_if_not_exists=True)
+        def configure(configuration: CodeSeederConfig) -> None:
+            LOGGER.debug(f"OPS ROOT (OPS_ROOT) is {self.OPS_ROOT}")
+            configuration.timeout = 120
+            configuration.codebuild_image = "public.ecr.aws/v3o4w1g6/aws-codeseeder/code-build-base:2.2.0"
+            configuration.pre_build_commands = [
+                (
+                    "nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock"
+                    " --host=tcp://127.0.0.1:2375 --storage-driver=overlay2 &"
+                ),
+                'timeout 15 sh -c "until docker info; do echo .; sleep 1; done"',
+            ]
+            configuration.python_modules = [f"seed-farmer=={__version__}"]
+            configuration.runtime_versions = {"nodejs": "14", "python": "3.9"}
+
+    @property
+    def PROJECT(self) -> str:
+        if self._PROJECT is None:
+            self._load_config_data()
+        return str(self._PROJECT)
+
+    @property
+    def DESCRIPTION(self) -> str:
+        if self._DESCRIPTION is None:
+            self._load_config_data()
+        return str(self._DESCRIPTION)
+
+
+config = Config()
diff --git a/seedfarmer/__main__.py b/seedfarmer/__main__.py
index a52fb9c7..671a17c3 100644
--- a/seedfarmer/__main__.py
+++ b/seedfarmer/__main__.py
@@ -13,40 +13,53 @@
 #    limitations under the License.
 
 
-import json
 import logging
-import sys
+import os
+from typing import Optional
 
 import click
-import yaml
+from dotenv import load_dotenv
 
 import seedfarmer
-import seedfarmer.mgmt.deploy_utils as du
-import seedfarmer.mgmt.module_info as mi
-import seedfarmer.mgmt.module_init as minit
-from seedfarmer import DEBUG_LOGGING_FORMAT, DESCRIPTION, PROJECT, commands, enable_debug, utils
-from seedfarmer.output_utils import print_bolded, print_deployment_inventory, print_json, print_manifest_inventory
+from seedfarmer import DEBUG_LOGGING_FORMAT, commands, config, enable_debug
+from seedfarmer.cli_groups import bootstrap, init, list, remove, store
+from seedfarmer.output_utils import print_bolded
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
+# Load environment variables from .env file if it exists
+load_dotenv(dotenv_path=os.path.join(config.OPS_ROOT, ".env"), verbose=True, override=True)
+
 
 @click.group()
 def cli() -> None:
-    f"{DESCRIPTION}"
+    """SeedFarmer CLI interface"""
     pass
 
 
-@click.command(help="Get the version of seedfarmer")
-def version():
-    print(f"seedfarmer {seedfarmer.__version__}")
+@click.command(help="Get the version SeedFarmer")
+def version() -> None:
+    print(f"seed-farmer-{seedfarmer.__version__}")
 
 
-@click.command(help=f"Apply a deployment manifest relative path for {PROJECT.upper()}")
+@click.command(help="Apply manifests to a SeedFarmer managed deployment")
 @click.argument(
     "spec",
     type=str,
     required=True,
 )
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region to use for boto3.Sessions",
+    required=False,
+)
 @click.option(
     "--debug/--no-debug",
     default=False,
@@ -65,18 +78,22 @@ def version():
     help="Write out the generated deployment manifest",
     show_default=True,
 )
-def apply(spec: str, debug: bool, dry_run: bool, show_manifest: bool) -> None:
-    f"Deploy a(n) {PROJECT.upper()} environemnt based on a deployspec file (yaml)." ""
+def apply(
+    spec: str, profile: Optional[str], region: Optional[str], debug: bool, dry_run: bool, show_manifest: bool
+) -> None:
+    """Apply manifests to a SeedFarmer managed deployment"""
     if debug:
         enable_debug(format=DEBUG_LOGGING_FORMAT)
     _logger.info("Apply request with manifest %s", spec)
     if dry_run:
         print_bolded(" ***   This is a dry-run...NO ACTIONS WILL BE TAKEN  *** ", "white")
 
-    commands.apply(spec, dry_run, show_manifest)
+    commands.apply(
+        deployment_manifest_path=spec, profile=profile, region_name=region, dryrun=dry_run, show_manifest=show_manifest
+    )
 
 
-@click.command(help=f"Destroy {PROJECT.upper()} Deployment")
+@click.command(help="Destroy a SeedFarmer managed deployment")
 @click.argument(
     "deployment",
     type=str,
@@ -95,365 +112,16 @@ def apply(spec: str, debug: bool, dry_run: bool, show_manifest: bool) -> None:
     show_default=True,
 )
 @click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def destroy(
-    deployment: str,
-    dry_run: bool,
-    show_manifest: bool,
-    debug: bool,
-) -> None:
-    f"Destroy an {PROJECT.upper} deployment ."
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.info("Destroy for %s", deployment)
-    if dry_run:
-        print_bolded(" ***   This is a dry-run...NO ACTIONS WILL BE TAKEN  *** ", "white")
-    commands.destroy(deployment_name=deployment, dryrun=dry_run, show_manifest=show_manifest)
-
-
-@click.group(name="store", help="Top Level command to support storing module metadata")
-def store() -> None:
-    f"Store module data for {PROJECT.upper()}"
-    pass
-
-
-@store.command(name="deployspec", help="Store/Update a deployspec of a currently deployed module")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--group",
-    "-g",
-    type=str,
-    help="The Group Name",
-    required=True,
-)
-@click.option(
-    "--module",
-    "-m",
-    type=str,
-    help="The Module Name",
-    required=True,
-)
-@click.option(
-    "--path",
-    "-p",
-    type=str,
-    help="The relative module path (ex. modules/optionals/networking) -- *** DO NOT PASS IN filename `deployspec.yaml`",
-    required=True,
-)
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def store_deployspec(
-    deployment: str,
-    group: str,
-    module: str,
-    path: str,
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-
-    _logger.debug(
-        f"""Storing deployspec for module {module} of group {group}
-        in deployment {deployment} located at {path}/deployspec.yaml"""
-    )
-    du.update_deployspec(deployment, group, module, path)
-
-
-@store.command(name="moduledata", help="CAT or pipe in a json or yaml object")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--group",
-    "-g",
-    type=str,
-    help="The Group Name",
-    required=True,
-)
-@click.option(
-    "--module",
-    "-m",
-    type=str,
-    help="The Module Name",
-    required=True,
-)
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def store_module_metadata(
-    deployment: str,
-    group: str,
-    module: str,
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("Writing metadata for module %s in deployment %s", module, deployment)
-    d = yaml.load(sys.stdin.read(), Loader=utils.CfnSafeYamlLoader)
-    if d:
-        mi.write_metadata(deployment=deployment, group=group, module=module, data=d)
-    else:
-        _logger.info("No Data avaiable...skipping")
-
-
-@store.command(name="md5", help="CAT or pipe in a string")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--group",
-    "-g",
-    type=str,
-    help="The Group Name",
-    required=True,
-)
-@click.option(
-    "--module",
-    "-m",
-    type=str,
-    help="The Module Name",
-    required=True,
-)
-@click.option(
-    "--type",
-    "-t",
-    type=str,
-    help="The kind of MD5: bundle or spec",
-    required=True,
-)
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def store_module_md5(
-    deployment: str,
-    group: str,
-    module: str,
-    type: str,
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("Writing md5 of  %s for module %s in deployment %s", type, module, deployment)
-    d = sys.stdin.readline().strip()
-    if d:
-        if type.casefold() == "bundle":
-            _type = mi.ModuleConst.BUNDLE
-        elif type.casefold() == "spec":
-            _type = mi.ModuleConst.DEPLOYSPEC
-        mi.write_module_md5(deployment=deployment, group=group, module=module, hash=d, type=_type)
-    else:
-        _logger.info("No Data avaiable...skipping")
-
-
-@click.group(name="list", help="List the relative data (module or deployment)")
-def list() -> None:
-    f"""List module data for {PROJECT.upper()}"""
-    pass
-
-
-@list.command(name="deployspec", help="List the stored deployspec of a module")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--group",
-    "-g",
-    type=str,
-    help="The Group Name",
-    required=True,
-)
-@click.option(
-    "--module",
-    "-m",
-    type=str,
-    help="The Module Name",
-    required=True,
-)
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def list_deployspec(
-    deployment: str,
-    group: str,
-    module: str,
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("We are getting deployspec for  %s in %s of deployment %s", module, group, deployment)
-
-    val = mi.get_deployspec(deployment=deployment, group=group, module=module)
-    print_json(val)
-
-
-@list.command(name="moduledata", help="Fetch the module metadata")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--group",
-    "-g",
-    type=str,
-    help="The Group Name",
-    required=True,
-)
-@click.option(
-    "--module",
-    "-m",
-    type=str,
-    help="The Module Name",
-    required=True,
-)
-@click.option(
-    "--export-local-env/--no-export-local-env",
-    default=False,
-    help="Print the moduledata as env parameters for local development support INSTEAD of json (default is FALSE)",
-    show_default=True,
-)
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def list_module_metadata(
-    deployment: str,
-    group: str,
-    module: str,
-    export_local_env: str,
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("We are getting module data for  %s in %s of %s", module, group, deployment)
-    metadata_json = mi.get_module_metadata(deployment, group, module)
-    if not export_local_env:
-        sys.stdout.write(json.dumps(metadata_json))
-    else:
-        envs = commands.generate_export_env_params(metadata_json)
-        if envs:
-            for exp in envs:
-                sys.stdout.write(exp)
-                sys.stdout.write("\n")
-        else:
-            print(f"No module data found for {deployment}-{group}-{module}")
-            print_bolded("To see all deployments, run seedfarmer list deployments")
-            print_bolded(f"To see all deployed modules in {deployment}, run seedfarmer list modules -d {deployment}")
-
-
-@list.command(name="modules", help="List the modules in a group")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def list_modules(
-    deployment: str,
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("We are getting modules for %s", deployment)
-
-    cache = du.generate_deployment_cache(deployment_name=deployment)
-    dep_manifest = du.generate_deployed_manifest(
-        deployment_name=deployment, deployment_params_cache=cache, skip_deploy_spec=True
-    )
-    if dep_manifest:
-        print_manifest_inventory("Deployed Modules", dep_manifest, False, "green")
-
-
-@list.command(name="deployments", help="List the deployments in this account")
-@click.option(
-    "--debug/--no-debug",
-    default=False,
-    help="Enable detailed logging.",
-    show_default=True,
-)
-def list_deployments(
-    debug: bool,
-) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("We are getting all deployments")
-    deps = mi.get_all_deployments()
-    print_deployment_inventory(description="Deployment Names", dep=deps)
-
-
-@click.group(name="remove", help="Top Level command to support removing module metadata")
-def remove() -> None:
-    f"""Remove module data for {PROJECT.upper()}"""
-    pass
-
-
-@remove.command(name="moduledata", help="Remove all SSM parameters tied to the module")
-@click.option(
-    "--deployment",
-    "-d",
-    type=str,
-    help="The Deployment Name",
-    required=True,
-)
-@click.option(
-    "--group",
-    "-g",
-    type=str,
-    help="The Group Name",
-    required=True,
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
 )
 @click.option(
-    "--module",
-    "-m",
-    type=str,
-    help="The Module Name",
-    required=True,
+    "--region",
+    default=None,
+    help="The AWS region to use for toolchain",
+    required=False,
 )
 @click.option(
     "--debug/--no-debug",
@@ -461,73 +129,27 @@ def remove() -> None:
     help="Enable detailed logging.",
     show_default=True,
 )
-def remove_module_data(
+def destroy(
     deployment: str,
-    group: str,
-    module: str,
+    dry_run: bool,
+    show_manifest: bool,
+    profile: Optional[str],
+    region: Optional[str],
     debug: bool,
 ) -> None:
+    """Destroy a SeedFarmer managed deployment"""
     if debug:
         enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug(" We are removing module data for %s of group %s in %s", module, group, deployment)
-
-    mi.remove_module_info(deployment, group, module)
-
-
-@click.group(name="init", help="Initiaize a project or module")
-def init() -> None:
-    f"""Initialize a project or module for {PROJECT.upper()}"""
-    pass
-
-
-@init.command(
-    name="project",
-    help=(
-        "Initialize a project. " "Make sure seedfarmer.yaml is present in the same location you execute this command!!"
-    ),
-)
-@click.option(
-    "--template-url",
-    "-t",
-    default="https://github.com/awslabs/seed-farmer.git",
-    help=(
-        "The template URL. If not specified, the default template repo is " "`https://github.com/awslabs/seed-farmer`"
-    ),
-    required=False,
-)
-def init_project(template_url: str) -> None:
-    minit.create_project(template_url=template_url)
+    # MUST use seedfarmer.yaml so we can initialize codeseeder configs
+    project = config.PROJECT
+    _logger.debug("Listing all deployments for Project %s", project)
 
+    _logger.info("Destroy for Project %s, Deployment %s", project, deployment)
+    if dry_run:
+        print_bolded(" ***   This is a dry-run...NO ACTIONS WILL BE TAKEN  *** ", "white")
 
-@init.command(name="module", help="Initialize a new module")
-@click.option(
-    "--group-name",
-    "-g",
-    type=str,
-    help="The group the module belongs to. The `group` is created if it doesn't exist",
-    required=False,
-    default=None,
-)
-@click.option("--module-name", "-m", type=str, help="The module name", required=True)
-@click.option(
-    "--template-url",
-    "-t",
-    default="https://github.com/awslabs/seed-farmer.git",
-    help=(
-        "The template URL. If not specified, the default template repo is " "`https://github.com/awslabs/seed-farmer`"
-    ),
-    required=False,
-)
-@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
-def init_module(group_name: str, module_name: str, template_url: str, debug: bool) -> None:
-    if debug:
-        enable_debug(format=DEBUG_LOGGING_FORMAT)
-    _logger.debug("Initializing module %s", module_name)
-
-    minit.create_module_dir(
-        group_name=group_name,
-        module_name=module_name,
-        template_url=template_url,
+    commands.destroy(
+        deployment_name=deployment, profile=profile, region_name=region, dryrun=dry_run, show_manifest=show_manifest
     )
 
 
@@ -539,5 +161,6 @@ def main() -> int:
     cli.add_command(list)
     cli.add_command(init)
     cli.add_command(version)
+    cli.add_command(bootstrap)
     cli()
     return 0
diff --git a/seedfarmer/cli_groups/__init__.py b/seedfarmer/cli_groups/__init__.py
new file mode 100644
index 00000000..0d046814
--- /dev/null
+++ b/seedfarmer/cli_groups/__init__.py
@@ -0,0 +1,21 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+from seedfarmer.cli_groups._bootstrap_group import bootstrap
+from seedfarmer.cli_groups._init_group import init
+from seedfarmer.cli_groups._list_group import list
+from seedfarmer.cli_groups._remove_group import remove
+from seedfarmer.cli_groups._store_group import store
+
+__all__ = ["bootstrap", "init", "list", "remove", "store"]
diff --git a/seedfarmer/cli_groups/_bootstrap_group.py b/seedfarmer/cli_groups/_bootstrap_group.py
new file mode 100644
index 00000000..26692c38
--- /dev/null
+++ b/seedfarmer/cli_groups/_bootstrap_group.py
@@ -0,0 +1,190 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+
+import logging
+from typing import List, Optional
+
+import click
+
+from seedfarmer import DEBUG_LOGGING_FORMAT, config, enable_debug
+from seedfarmer.commands import bootstrap_target_account, bootstrap_toolchain_account
+from seedfarmer.output_utils import print_bolded
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+def _load_project() -> str:
+    try:
+        print_bolded("No --project provided, attempting load from seedfarmer.yaml", "white")
+        return config.PROJECT
+    except FileNotFoundError:
+        print_bolded("Unable to determine project to bootstrap, one of --project or a seedfarmer.yaml is required")
+        raise click.ClickException("Failed to determine project identifier")
+
+
+@click.group(name="bootstrap", help="Bootstrap (initialize) a Toolchain or Target account")
+def bootstrap() -> None:
+    """Bootstrap a Toolchain or Target account"""
+    pass
+
+
+@bootstrap.command(
+    name="toolchain",
+    help="Bootstrap a Toolchain account.",
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--trusted-principal",
+    "-t",
+    help="ARN of Principals trusted to assume the Toolchain Role",
+    multiple=True,
+    required=False,
+    default=[],
+)
+@click.option(
+    "--permissions-boundary",
+    "-b",
+    help="ARN of a Managed Policy to set as the Permission Boundary on the Toolchain Role",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--as-target/--not-as-target",
+    type=bool,
+    default=False,
+    help="Optionally also bootstrap the account as a Target account",
+    required=False,
+    show_default=True,
+)
+@click.option(
+    "--synth/--no-synth",
+    type=bool,
+    default=False,
+    help="Synthesize a CFN template only...do not deploy",
+    required=False,
+    show_default=True,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to initiate a session",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="AWS region to use",
+    required=False,
+)
+@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
+def bootstrap_toolchain(
+    project: Optional[str],
+    trusted_principal: List[str],
+    permissions_boundary: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    as_target: bool,
+    synth: bool,
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    if project is None:
+        project = _load_project()
+    _logger.debug("Bootstrapping a Toolchain account for Project %s", project)
+    bootstrap_toolchain_account(
+        project_name=project,
+        principal_arns=trusted_principal,
+        permissions_boundary_arn=permissions_boundary,
+        profile=profile,
+        region_name=region,
+        synthesize=synth,
+        as_target=as_target,
+    )
+
+
+@bootstrap.command(
+    name="target",
+    help="Bootstrap a Target account.",
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--toolchain-account",
+    "-t",
+    required=True,
+    help="Account Id of the Toolchain account trusted to assume the Target account's Deployment Role",
+)
+@click.option(
+    "--permissions-boundary",
+    "-b",
+    help="ARN of a Managed Policy to set as the Permission Boundary on the Toolchain Role",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--synth/--no-synth",
+    type=bool,
+    default=False,
+    help="Synthesize a CFN template only...do not deploy",
+    required=False,
+    show_default=True,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to initiate a session",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="AWS region to use",
+    required=False,
+)
+@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
+def bootstrap_target(
+    project: Optional[str],
+    toolchain_account: str,
+    permissions_boundary: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    synth: bool,
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    if project is None:
+        project = _load_project()
+    _logger.debug("Bootstrapping a Target account for Project %s", project)
+    bootstrap_target_account(
+        toolchain_account_id=toolchain_account,
+        project_name=project,
+        profile=profile,
+        region_name=region,
+        permissions_boundary_arn=permissions_boundary,
+        synthesize=synth,
+    )
diff --git a/seedfarmer/cli_groups/_init_group.py b/seedfarmer/cli_groups/_init_group.py
new file mode 100644
index 00000000..b4fbd255
--- /dev/null
+++ b/seedfarmer/cli_groups/_init_group.py
@@ -0,0 +1,75 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+import logging
+
+import click
+
+import seedfarmer.mgmt.module_init as minit
+from seedfarmer import DEBUG_LOGGING_FORMAT, enable_debug
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+@click.group(name="init", help="Initiaize a project or module")
+def init() -> None:
+    """Initialize a project or module"""
+    pass
+
+
+@init.command(
+    name="project",
+    help="Initialize a project. Make sure seedfarmer.yaml is present in the same location you execute this command!!",
+)
+@click.option(
+    "--template-url",
+    "-t",
+    default="https://github.com/awslabs/seed-farmer.git",
+    help="The template URL. If not specified, the default template repo is `https://github.com/awslabs/seed-farmer`",
+    required=False,
+)
+def init_project(template_url: str) -> None:
+    minit.create_project(template_url=template_url)
+
+
+@init.command(name="module", help="Initialize a new module")
+@click.option(
+    "--group-name",
+    "-g",
+    type=str,
+    help="The group the module belongs to. The `group` is created if it doesn't exist",
+    required=False,
+    default=None,
+)
+@click.option("--module-name", "-m", type=str, help="The module name", required=True)
+@click.option(
+    "--template-url",
+    "-t",
+    default="https://github.com/awslabs/seed-farmer.git",
+    help=(
+        "The template URL. If not specified, the default template repo is " "`https://github.com/awslabs/seed-farmer`"
+    ),
+    required=False,
+)
+@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
+def init_module(group_name: str, module_name: str, template_url: str, debug: bool) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("Initializing module %s", module_name)
+
+    minit.create_module_dir(
+        group_name=group_name,
+        module_name=module_name,
+        template_url=template_url,
+    )
diff --git a/seedfarmer/cli_groups/_list_group.py b/seedfarmer/cli_groups/_list_group.py
new file mode 100644
index 00000000..fcb0c151
--- /dev/null
+++ b/seedfarmer/cli_groups/_list_group.py
@@ -0,0 +1,322 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+import json
+import logging
+import sys
+from typing import Optional
+
+import click
+
+import seedfarmer.mgmt.deploy_utils as du
+import seedfarmer.mgmt.module_info as mi
+from seedfarmer import DEBUG_LOGGING_FORMAT, commands, config, enable_debug
+from seedfarmer.output_utils import print_bolded, print_deployment_inventory, print_json, print_manifest_inventory
+from seedfarmer.services.session_manager import SessionManager
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+def _load_project() -> str:
+    try:
+        print_bolded("No --project provided, attempting load from seedfarmer.yaml", "white")
+        return config.PROJECT
+    except FileNotFoundError:
+        print_bolded("Unable to determine project to bootstrap, one of --project or a seedfarmer.yaml is required")
+        raise click.ClickException("Failed to determine project identifier")
+
+
+@click.group(name="list", help="List the relative data (module or deployment)")
+def list() -> None:
+    """List module data"""
+    pass
+
+
+@list.command(name="deployspec", help="List the stored deployspec of a module")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--group",
+    "-g",
+    type=str,
+    help="The Group Name",
+    required=True,
+)
+@click.option(
+    "--module",
+    "-m",
+    type=str,
+    help="The Module Name",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def list_deployspec(
+    deployment: str,
+    group: str,
+    module: str,
+    project: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("We are getting deployspec for  %s in %s of deployment %s", module, group, deployment)
+
+    if project is None:
+        project = _load_project()
+
+    session = SessionManager().get_or_create(project_name=project, profile=profile, region_name=region)
+    dep_manifest = du.generate_deployed_manifest(deployment_name=deployment, skip_deploy_spec=True)
+
+    if dep_manifest is None:
+        print_json({})
+        return
+
+    dep_manifest.validate_and_set_module_defaults()
+    session = session.get_deployment_session(
+        account_id=dep_manifest.get_module(group=group, module=module).get_target_account_id(),  # type: ignore
+        region_name=dep_manifest.get_module(group=group, module=module).target_region,  # type: ignore
+    )
+
+    val = mi.get_deployspec(deployment=deployment, group=group, module=module, session=session)
+    print_json(val)
+
+
+@list.command(name="moduledata", help="Fetch the module metadata")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--group",
+    "-g",
+    type=str,
+    help="The Group Name",
+    required=True,
+)
+@click.option(
+    "--module",
+    "-m",
+    type=str,
+    help="The Module Name",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--export-local-env/--no-export-local-env",
+    default=False,
+    help="Print the moduledata as env parameters for local development support INSTEAD of json (default is FALSE)",
+    show_default=True,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def list_module_metadata(
+    deployment: str,
+    group: str,
+    module: str,
+    project: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    export_local_env: str,
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("We are getting module data for  %s in %s of %s", module, group, deployment)
+
+    if project is None:
+        project = _load_project()
+
+    session = SessionManager().get_or_create(project_name=project, profile=profile, region_name=region)
+    dep_manifest = du.generate_deployed_manifest(deployment_name=deployment, skip_deploy_spec=True)
+
+    if dep_manifest is None:
+        print(f"No module data found for {deployment}-{group}-{module}")
+        print_bolded("To see all deployments, run seedfarmer list deployments")
+        print_bolded(f"To see all deployed modules in {deployment}, run seedfarmer list modules -d {deployment}")
+        return
+
+    dep_manifest.validate_and_set_module_defaults()
+    session = session.get_deployment_session(
+        account_id=dep_manifest.get_module(group=group, module=module).get_target_account_id(),  # type: ignore
+        region_name=dep_manifest.get_module(group=group, module=module).target_region,  # type: ignore
+    )
+
+    metadata_json = mi.get_module_metadata(deployment, group, module, session=session)
+    if not export_local_env:
+        sys.stdout.write(json.dumps(metadata_json))
+    else:
+        envs = commands.generate_export_env_params(metadata_json)
+        if envs:
+            for exp in envs:
+                sys.stdout.write(exp)
+                sys.stdout.write("\n")
+        else:
+            print(f"No module data found for {deployment}-{group}-{module}")
+            print_bolded("To see all deployments, run seedfarmer list deployments")
+            print_bolded(f"To see all deployed modules in {deployment}, run seedfarmer list modules -d {deployment}")
+
+
+@list.command(name="modules", help="List the modules in a deployment")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def list_modules(
+    deployment: str,
+    project: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("We are getting modules for %s", deployment)
+
+    if project is None:
+        project = _load_project()
+    SessionManager().get_or_create(project_name=project, profile=profile, region_name=region)
+
+    dep_manifest = du.generate_deployed_manifest(deployment_name=deployment, skip_deploy_spec=True)
+    if dep_manifest:
+        print_manifest_inventory("Deployed Modules", dep_manifest, False, "green")
+
+
+@list.command(name="deployments", help="List the deployments in this account")
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def list_deployments(
+    project: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    if project is None:
+        project = _load_project()
+    _logger.debug("Listing all deployments for Project %s", project)
+
+    deps = mi.get_all_deployments(
+        session=SessionManager()
+        .get_or_create(project_name=project, profile=profile, region_name=region)
+        .toolchain_session
+    )
+    print_deployment_inventory(description="Deployment Names", dep=deps)
diff --git a/seedfarmer/cli_groups/_remove_group.py b/seedfarmer/cli_groups/_remove_group.py
new file mode 100644
index 00000000..e9706232
--- /dev/null
+++ b/seedfarmer/cli_groups/_remove_group.py
@@ -0,0 +1,131 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+import logging
+from typing import Optional
+
+import click
+from boto3 import Session
+
+import seedfarmer.mgmt.module_info as mi
+from seedfarmer import DEBUG_LOGGING_FORMAT, config, enable_debug
+from seedfarmer.output_utils import print_bolded
+from seedfarmer.services.session_manager import SessionManager
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+def _load_project() -> str:
+    try:
+        print_bolded("No --project provided, attempting load from seedfarmer.yaml", "white")
+        return config.PROJECT
+    except FileNotFoundError:
+        print_bolded("Unable to determine project to bootstrap, one of --project or a seedfarmer.yaml is required")
+        raise click.ClickException("Failed to determine project identifier")
+
+
+@click.group(name="remove", help="Top Level command to support removing module metadata")
+def remove() -> None:
+    """Remove module data"""
+    pass
+
+
+@remove.command(name="moduledata", help="Remove all SSM parameters tied to the module")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--group",
+    "-g",
+    type=str,
+    help="The Group Name",
+    required=True,
+)
+@click.option(
+    "--module",
+    "-m",
+    type=str,
+    help="The Module Name",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for boto3.Sessions",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--target-account-id",
+    default=None,
+    help="Account Id to remove module data from, if specifed --target-region is required",
+    show_default=True,
+)
+@click.option(
+    "--target-region",
+    default=None,
+    help="Region to remove module data from, if specifed --target-account-id is required",
+    show_default=True,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def remove_module_data(
+    deployment: str,
+    group: str,
+    module: str,
+    profile: Optional[str],
+    region: Optional[str],
+    project: Optional[str],
+    target_account_id: Optional[str],
+    target_region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("We are removing module data for %s of group %s in %s", module, group, deployment)
+
+    if project is None:
+        project = _load_project()
+
+    session: Optional[Session] = None
+    if (target_account_id is not None) != (target_region is not None):
+        raise ValueError("Must either specify both --target-account-id and --target-region, or neither")
+    elif target_account_id is not None and target_region is not None:
+        session = (
+            SessionManager()
+            .get_or_create(project_name=project, profile=profile, region_name=region)
+            .get_deployment_session(account_id=target_account_id, region_name=target_region)
+        )
+
+    mi.remove_module_info(deployment, group, module, session=session)
diff --git a/seedfarmer/cli_groups/_store_group.py b/seedfarmer/cli_groups/_store_group.py
new file mode 100644
index 00000000..bf0d86b6
--- /dev/null
+++ b/seedfarmer/cli_groups/_store_group.py
@@ -0,0 +1,347 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+import logging
+import sys
+from typing import Optional
+
+import click
+import yaml
+from boto3 import Session
+
+import seedfarmer.mgmt.deploy_utils as du
+import seedfarmer.mgmt.module_info as mi
+from seedfarmer import DEBUG_LOGGING_FORMAT, config, enable_debug, utils
+from seedfarmer.output_utils import print_bolded
+from seedfarmer.services.session_manager import SessionManager
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+def _load_project() -> str:
+    try:
+        print_bolded("No --project provided, attempting load from seedfarmer.yaml", "white")
+        return config.PROJECT
+    except FileNotFoundError:
+        print_bolded("Unable to determine project to bootstrap, one of --project or a seedfarmer.yaml is required")
+        raise click.ClickException("Failed to determine project identifier")
+
+
+@click.group(name="store", help="Top Level command to support storing module metadata")
+def store() -> None:
+    "Store module data"
+    pass
+
+
+@store.command(name="deployspec", help="Store/Update a deployspec of a currently deployed module")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--group",
+    "-g",
+    type=str,
+    help="The Group Name",
+    required=True,
+)
+@click.option(
+    "--module",
+    "-m",
+    type=str,
+    help="The Module Name",
+    required=True,
+)
+@click.option(
+    "--path",
+    "-p",
+    type=str,
+    help="The relative module path (ex. modules/optionals/networking) -- *** DO NOT PASS IN filename `deployspec.yaml`",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for assuming toolchain role",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--target-account-id",
+    default=None,
+    help="Account Id to remove module data from, if specifed --target-region is required",
+    show_default=True,
+)
+@click.option(
+    "--target-region",
+    default=None,
+    help="Region to remove module data from, if specifed --target-account-id is required",
+    show_default=True,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def store_deployspec(
+    deployment: str,
+    group: str,
+    module: str,
+    path: str,
+    profile: Optional[str],
+    region: Optional[str],
+    project: Optional[str],
+    target_account_id: Optional[str],
+    target_region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+
+    _logger.debug(
+        f"""Storing deployspec for module {module} of group {group}
+        in deployment {deployment} located at {path}/deployspec.yaml"""
+    )
+
+    if project is None:
+        project = _load_project()
+
+    session: Session = Session(profile_name=profile, region_name=region)
+    if (target_account_id is not None) != (target_region is not None):
+        raise ValueError("Must either specify both --target-account-id and --target-region, or neither")
+    elif target_account_id is not None and target_region is not None:
+        session = (
+            SessionManager()
+            .get_or_create(project_name=project, profile=profile, region_name=region)
+            .get_deployment_session(account_id=target_account_id, region_name=target_region)
+        )
+
+    du.update_deployspec(deployment, group, module, path, session=session)
+
+
+@store.command(name="moduledata", help="CAT or pipe in a json or yaml object")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--group",
+    "-g",
+    type=str,
+    help="The Group Name",
+    required=True,
+)
+@click.option(
+    "--module",
+    "-m",
+    type=str,
+    help="The Module Name",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for asssuming toolchain role",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--target-account-id",
+    default=None,
+    help="Account Id to remove module data from, if specifed --target-region is required",
+    show_default=True,
+)
+@click.option(
+    "--target-region",
+    default=None,
+    help="Region to remove module data from, if specifed --target-account-id is required",
+    show_default=True,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def store_module_metadata(
+    deployment: str,
+    group: str,
+    module: str,
+    project: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    target_account_id: Optional[str],
+    target_region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("Writing metadata for module %s in deployment %s", module, deployment)
+
+    if project is None:
+        project = _load_project()
+
+    session: Session = Session(profile_name=profile, region_name=region)
+
+    if (target_account_id is not None) != (target_region is not None):
+        raise ValueError("Must either specify both --target-account-id and --target-region, or neither")
+    elif target_account_id is not None and target_region is not None:
+        session = (
+            SessionManager()
+            .get_or_create(project_name=project, profile=profile, region_name=region)
+            .get_deployment_session(account_id=target_account_id, region_name=target_region)
+        )
+
+    d = yaml.load(sys.stdin.read(), Loader=utils.CfnSafeYamlLoader)
+    if d:
+        mi.write_metadata(deployment=deployment, group=group, module=module, data=d, session=session)
+    else:
+        _logger.info("No Data avaiable...skipping")
+
+
+@store.command(name="md5", help="CAT or pipe in a string")
+@click.option(
+    "--deployment",
+    "-d",
+    type=str,
+    help="The Deployment Name",
+    required=True,
+)
+@click.option(
+    "--group",
+    "-g",
+    type=str,
+    help="The Group Name",
+    required=True,
+)
+@click.option(
+    "--module",
+    "-m",
+    type=str,
+    help="The Module Name",
+    required=True,
+)
+@click.option(
+    "--type",
+    "-t",
+    type=str,
+    help="The kind of MD5: bundle or spec",
+    required=True,
+)
+@click.option(
+    "--project",
+    "-p",
+    help="Project identifier",
+    required=False,
+    default=None,
+)
+@click.option(
+    "--profile",
+    default=None,
+    help="The AWS profile to use for asssuming toolchain role",
+    required=False,
+)
+@click.option(
+    "--region",
+    default=None,
+    help="The AWS region of the toolchain",
+    required=False,
+)
+@click.option(
+    "--target-account-id",
+    default=None,
+    help="Account Id to remove module data from, if specifed --target-region is required",
+    show_default=True,
+)
+@click.option(
+    "--target-region",
+    default=None,
+    help="Region to remove module data from, if specifed --target-account-id is required",
+    show_default=True,
+)
+@click.option(
+    "--debug/--no-debug",
+    default=False,
+    help="Enable detailed logging.",
+    show_default=True,
+)
+def store_module_md5(
+    deployment: str,
+    group: str,
+    module: str,
+    type: str,
+    project: Optional[str],
+    target_account_id: Optional[str],
+    target_region: Optional[str],
+    profile: Optional[str],
+    region: Optional[str],
+    debug: bool,
+) -> None:
+    if debug:
+        enable_debug(format=DEBUG_LOGGING_FORMAT)
+    _logger.debug("Writing md5 of  %s for module %s in deployment %s", type, module, deployment)
+
+    if project is None:
+        project = _load_project()
+
+    session: Session = Session(profile_name=profile, region_name=region)
+    if (target_account_id is not None) != (target_region is not None):
+        raise ValueError("Must either specify both --target-account-id and --target-region, or neither")
+    elif target_account_id is not None and target_region is not None:
+        session = (
+            SessionManager()
+            .get_or_create(project_name=project, profile=profile, region_name=region)
+            .get_deployment_session(account_id=target_account_id, region_name=target_region)
+        )
+
+    d = sys.stdin.readline().strip()
+    if d:
+        if type.casefold() == "bundle":
+            _type = mi.ModuleConst.BUNDLE
+        elif type.casefold() == "spec":
+            _type = mi.ModuleConst.DEPLOYSPEC
+        mi.write_module_md5(deployment=deployment, group=group, module=module, hash=d, type=_type, session=session)
+    else:
+        _logger.info("No Data avaiable...skipping")
diff --git a/seedfarmer/commands/__init__.py b/seedfarmer/commands/__init__.py
index d185de0b..4b0e571a 100644
--- a/seedfarmer/commands/__init__.py
+++ b/seedfarmer/commands/__init__.py
@@ -12,6 +12,7 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
+from seedfarmer.commands._bootstrap_commands import bootstrap_target_account, bootstrap_toolchain_account
 from seedfarmer.commands._deployment_commands import apply, destroy
 from seedfarmer.commands._module_commands import deploy_module, destroy_module
 from seedfarmer.commands._parameter_commands import generate_export_env_params
@@ -19,7 +20,9 @@
     deploy_managed_policy_stack,
     deploy_module_stack,
     deploy_seedkit,
+    destroy_managed_policy_stack,
     destroy_module_stack,
+    destroy_seedkit,
 )
 
 __all__ = [
@@ -28,8 +31,12 @@
     "deploy_module",
     "destroy_module",
     "deploy_managed_policy_stack",
+    "destroy_managed_policy_stack",
     "deploy_module_stack",
     "destroy_module_stack",
     "deploy_seedkit",
+    "destroy_seedkit",
     "generate_export_env_params",
+    "bootstrap_toolchain_account",
+    "bootstrap_target_account",
 ]
diff --git a/seedfarmer/commands/_bootstrap_commands.py b/seedfarmer/commands/_bootstrap_commands.py
new file mode 100644
index 00000000..e6c63281
--- /dev/null
+++ b/seedfarmer/commands/_bootstrap_commands.py
@@ -0,0 +1,167 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+import json
+import logging
+import os
+import sys
+from typing import Any, Dict, List, Optional, Tuple
+
+import yaml
+from aws_codeseeder import services as cs_services
+from boto3 import Session
+from botocore.exceptions import WaiterError
+from jinja2 import Template
+
+from seedfarmer import CLI_ROOT
+from seedfarmer.services import create_new_session, get_account_id, get_region
+from seedfarmer.services._iam import get_role
+from seedfarmer.utils import CfnSafeYamlLoader
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+def get_toolchain_template(
+    project_name: str,
+    principal_arn: List[str],
+    permissions_boundary_arn: Optional[str] = None,
+) -> Dict[Any, Any]:
+    with open((os.path.join(CLI_ROOT, "resources/toolchain_role.template")), "r") as f:
+        role = yaml.load(f, CfnSafeYamlLoader)
+    if principal_arn:
+        role["Resources"]["ToolchainRole"]["Properties"]["AssumeRolePolicyDocument"]["Statement"][0]["Principal"][
+            "AWS"
+        ] = principal_arn
+    if permissions_boundary_arn:
+        role["Resources"]["ToolchainRole"]["Properties"]["PermissionsBoundary"] = permissions_boundary_arn
+    template = Template(json.dumps(role))
+    t = template.render({"project_name": project_name})
+    return dict(json.loads(t))
+
+
+def get_deployment_template(
+    toolchain_account_id: str, project_name: str, permissions_boundary_arn: Optional[str] = None
+) -> Dict[Any, Any]:
+    with open((os.path.join(CLI_ROOT, "resources/deployment_role.template")), "r") as f:
+        role = yaml.load(f, CfnSafeYamlLoader)
+    if permissions_boundary_arn:
+        role["Resources"]["DeploymentRole"]["Properties"]["PermissionsBoundary"] = permissions_boundary_arn
+    template = Template(json.dumps(role))
+    t = template.render({"toolchain_account_id": toolchain_account_id, "project_name": project_name})
+    return dict(json.loads(t))
+
+
+def write_template(template: Dict[Any, Any]) -> None:
+    yaml.dump(template, sys.stdout)
+
+
+def bootstrap_toolchain_account(
+    project_name: str,
+    principal_arns: List[str],
+    permissions_boundary_arn: Optional[str] = None,
+    profile: Optional[str] = None,
+    region_name: Optional[str] = None,
+    synthesize: bool = False,
+    as_target: bool = False,
+) -> Optional[Dict[Any, Any]]:
+    template = get_toolchain_template(project_name, principal_arns, permissions_boundary_arn)
+    _logger.debug((json.dumps(template, indent=4)))
+    if not synthesize:
+        session = create_new_session(profile=profile, region_name=region_name)
+        role_name, stack_name = f"seedfarmer-{project_name}-toolchain-role", f"seedfarmer-{project_name}-toolchain-role"
+        apply_deploy_logic(template=template, role_name=role_name, stack_name=stack_name, session=session)
+        if as_target:
+            bootstrap_target_account(
+                toolchain_account_id=get_account_id(session),
+                project_name=project_name,
+                permissions_boundary_arn=permissions_boundary_arn,
+                profile=profile,
+                region_name=region_name,
+                session=session,
+            )
+    else:
+        write_template(template=template)
+    return template
+
+
+def bootstrap_target_account(
+    toolchain_account_id: str,
+    project_name: str,
+    permissions_boundary_arn: Optional[str] = None,
+    profile: Optional[str] = None,
+    region_name: Optional[str] = None,
+    session: Optional[Session] = None,
+    synthesize: bool = False,
+) -> Optional[Dict[Any, Any]]:
+    template = get_deployment_template(toolchain_account_id, project_name, permissions_boundary_arn)
+    _logger.debug((json.dumps(template, indent=4)))
+    if not synthesize:
+        if not session:
+            session = create_new_session(profile=profile, region_name=region_name)
+        role_name, stack_name = (
+            f"seedfarmer-{project_name}-deployment-role",
+            f"seedfarmer-{project_name}-deployment-role",
+        )
+        apply_deploy_logic(template=template, role_name=role_name, stack_name=stack_name, session=session)
+    else:
+        write_template(template=template)
+    return template
+
+
+def apply_deploy_logic(template: Dict[Any, Any], role_name: str, stack_name: str, session: Session) -> None:
+    role_exists, stack_exists = role_deploy_status(role_name=role_name, stack_name=stack_name, session=session)
+    if not role_exists:
+        _logger.info(
+            "Deploying role in account %s, region %s", get_account_id(session=session), get_region(session=session)
+        )
+        deploy_template(template=template, stack_name=stack_name, session=session)
+    else:
+        if stack_exists[0]:
+            _logger.info(
+                "Updating role in account %s, region %s", get_account_id(session=session), get_region(session=session)
+            )
+            deploy_template(template=template, stack_name=stack_name, session=session)
+        else:
+            _logger.info(
+                "The role %s exists in account %s as was not deployed in region %s, it will NOT be updated",
+                role_name,
+                get_account_id(session=session),
+                get_region(session=session),
+            )
+
+
+def deploy_template(template: Dict[Any, Any], stack_name: str, session: Optional[Session]) -> None:
+    loc = os.path.join(os.getcwd(), "templates")
+    output = os.path.join(loc, f"{stack_name}.yaml")
+    os.makedirs(loc, exist_ok=True)
+    with open(output, "w") as outfile:
+        yaml.dump(template, outfile)
+    try:
+        cs_services.set_boto3_session(session) if session else None
+        cs_services.cfn.deploy_template(
+            stack_name=stack_name,
+            filename=output,
+        )
+        _logger.info(f"Role for Seed-Farmer deployed in stack {stack_name}")
+    except WaiterError as we:
+        _logger.error("Could not create the deployment role...make sure the toolchain role exists and check CFN Logs")
+        raise we
+    finally:
+        os.remove(output)
+
+
+def role_deploy_status(role_name: str, stack_name: str, session: Session) -> Tuple[Optional[Dict[str, Any]], Any]:
+    return get_role(role_name=role_name, session=session), cs_services.cfn.does_stack_exist(
+        stack_name=stack_name, session=session
+    )
diff --git a/seedfarmer/commands/_deployment_commands.py b/seedfarmer/commands/_deployment_commands.py
index d94b9d01..382248b4 100644
--- a/seedfarmer/commands/_deployment_commands.py
+++ b/seedfarmer/commands/_deployment_commands.py
@@ -17,25 +17,23 @@
 import json
 import logging
 import os
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, cast
 
 import checksumdir
 import yaml
 
 import seedfarmer.mgmt.deploy_utils as du
-from seedfarmer import OPS_ROOT, commands
+from seedfarmer import commands, config
 from seedfarmer.commands._parameter_commands import load_parameter_values
 from seedfarmer.mgmt.module_info import (
     _get_deployspec_path,
     _get_modulestack_path,
-    get_deployed_modules,
     get_module_metadata,
     remove_deployed_deployment_manifest,
     remove_deployment_manifest,
-    remove_group_info,
     write_deployment_manifest,
 )
-from seedfarmer.models.deploy_responses import ModuleDeploymentResponse
+from seedfarmer.models.deploy_responses import ModuleDeploymentResponse, StatusType
 from seedfarmer.models.manifests import DeploymentManifest, DeploySpec, ModuleManifest, ModulesManifest
 from seedfarmer.output_utils import (
     _print_modules,
@@ -44,87 +42,115 @@
     print_manifest_inventory,
     print_manifest_json,
 )
+from seedfarmer.services.session_manager import SessionManager
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
 
 def _execute_deploy(
-    d_name: str,
-    g_name: str,
-    m: ModuleManifest,
-    d_secret: Optional[str] = None,
-    permission_boundary_arn: Optional[str] = None,
+    group_name: str,
+    module_manifest: ModuleManifest,
+    deployment_manifest: DeploymentManifest,
+    docker_credentials_secret: Optional[str] = None,
+    permissions_boundary_arn: Optional[str] = None,
 ) -> ModuleDeploymentResponse:
-    parameters = load_parameter_values(deployment_name=d_name, parameters=m.parameters)
+    parameters = load_parameter_values(
+        deployment_name=deployment_manifest.name,
+        parameters=module_manifest.parameters,
+        deployment_manifest=deployment_manifest,
+    )
 
+    target_account_id = cast(str, module_manifest.get_target_account_id())
+    target_region = cast(str, module_manifest.target_region)
     # Deploys the IAM role per module
     commands.deploy_module_stack(
-        _get_modulestack_path(m.path),
-        d_name,
-        g_name,
-        m.name,
+        _get_modulestack_path(module_manifest.path),
+        deployment_manifest.name,
+        group_name,
+        module_manifest.name,
+        target_account_id,
+        target_region,
         parameters,
-        docker_credentials_secret=d_secret,
-        permission_boundary_arn=permission_boundary_arn,
+        docker_credentials_secret=docker_credentials_secret,
+        permissions_boundary_arn=permissions_boundary_arn,
     )
 
     #   Get the current module's SSM if it was alreadly loaded...
-    module_metadata = json.dumps(get_module_metadata(d_name, g_name, m.name))
+    session = (
+        SessionManager().get_or_create().get_deployment_session(account_id=target_account_id, region_name=target_region)
+    )
+    module_metadata = json.dumps(
+        get_module_metadata(deployment_manifest.name, group_name, module_manifest.name, session=session)
+    )
 
-    if m.deploy_spec is None:
-        raise ValueError(f"Invalid value for ModuleManifest.deploy_spec in group {g_name} and module : {m.name}")
+    if module_manifest.deploy_spec is None:
+        raise ValueError(
+            f"Invalid value for ModuleManifest.deploy_spec in group {group_name} and module : {module_manifest.name}"
+        )
 
     return commands.deploy_module(
-        deployment_name=d_name,
-        group_name=g_name,
-        module_path=os.path.join(OPS_ROOT, m.path),
-        module_deploy_spec=m.deploy_spec,
-        module_manifest_name=m.name,
+        deployment_name=deployment_manifest.name,
+        group_name=group_name,
+        module_path=os.path.join(config.OPS_ROOT, module_manifest.path),
+        module_deploy_spec=module_manifest.deploy_spec,
+        module_manifest_name=module_manifest.name,
+        account_id=target_account_id,
+        region=target_region,
         parameters=parameters,
         module_metadata=module_metadata,
-        module_bundle_md5=m.bundle_md5,
-        docker_credentials_secret=d_secret,
-        permission_boundary_arn=permission_boundary_arn,
+        module_bundle_md5=module_manifest.bundle_md5,
+        docker_credentials_secret=docker_credentials_secret,
+        permissions_boundary_arn=permissions_boundary_arn,
     )
 
 
 def _execute_destroy(
-    d_name: str,
-    g_name: str,
-    m: ModuleManifest,
-    d_secret: Optional[str] = None,
+    group_name: str,
+    module_manifest: ModuleManifest,
+    deployment_manifest: DeploymentManifest,
+    docker_credentials_secret: Optional[str] = None,
 ) -> Optional[ModuleDeploymentResponse]:
-    if m.deploy_spec is None:
-        raise ValueError(f"Invalid value for ModuleManifest.deploy_spec in group {g_name} and module : {m.name}")
+    if module_manifest.deploy_spec is None:
+        raise ValueError(
+            f"Invalid value for ModuleManifest.deploy_spec in group {group_name} and module : {module_manifest.name}"
+        )
 
     resp = commands.destroy_module(
-        deployment_name=d_name,
-        group_name=g_name,
-        module_path=m.path,
-        module_deploy_spec=m.deploy_spec,
-        module_manifest_name=m.name,
-        parameters=load_parameter_values(deployment_name=d_name, parameters=m.parameters),
+        deployment_name=deployment_manifest.name,
+        group_name=group_name,
+        module_path=module_manifest.path,
+        module_deploy_spec=module_manifest.deploy_spec,
+        module_manifest_name=module_manifest.name,
+        account_id=cast(str, module_manifest.get_target_account_id()),
+        region=cast(str, module_manifest.target_region),
+        parameters=load_parameter_values(
+            deployment_name=deployment_manifest.name,
+            parameters=module_manifest.parameters,
+            deployment_manifest=deployment_manifest,
+        ),
         module_metadata=None,
     )
-    commands.destroy_module_stack(
-        d_name,
-        g_name,
-        m.name,
-        docker_credentials_secret=d_secret,
-    )
 
-    if not get_deployed_modules(deployment=d_name, group=g_name):
-        remove_group_info(d_name, g_name)
+    if resp.status == StatusType.SUCCESS.value:
+        commands.destroy_module_stack(
+            deployment_manifest.name,
+            group_name,
+            module_manifest.name,
+            account_id=cast(str, module_manifest.get_target_account_id()),
+            region=cast(str, module_manifest.target_region),
+            docker_credentials_secret=docker_credentials_secret,
+        )
+
+    # TODO: Confirm whether this is needed, commenting for now
+    # if not get_deployed_modules(deployment=d_name, group=g_name):
+    #     remove_group_info(d_name, g_name)
     return resp
 
 
 def _deploy_deployment_is_not_dry_run(
     deployment_manifest: DeploymentManifest,
     deployment_manifest_wip: DeploymentManifest,
-    deployment_name: str,
     groups_to_deploy: List[ModulesManifest],
-    permission_boundary_arn: Optional[str],
-    docker_credentials_secret: Optional[str],
 ) -> None:
     if groups_to_deploy:
         deployment_manifest_wip.groups = groups_to_deploy
@@ -143,17 +169,35 @@ def _deploy_deployment_is_not_dry_run(
                 with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as workers:
 
                     def _exec_deploy(args: Dict[str, Any]) -> ModuleDeploymentResponse:
-                        return _execute_deploy(
-                            args["d_name"], args["g"], args["m"], args["d_secret"], args["permission_boundary_arn"]
+                        return _execute_deploy(**args)
+
+                    def _render_permissions_boundary_arn(
+                        account_id: Optional[str], permissions_boundary_name: Optional[str]
+                    ) -> Optional[str]:
+                        return (
+                            f"arn:aws:iam::{account_id}:policy/{permissions_boundary_name}"
+                            if permissions_boundary_name is not None
+                            else None
                         )
 
                     params = [
                         {
-                            "d_name": deployment_name,
-                            "g": _group.name,
-                            "m": _module,
-                            "d_secret": docker_credentials_secret,
-                            "permission_boundary_arn": permission_boundary_arn,
+                            "group_name": _group.name,
+                            "module_manifest": _module,
+                            "deployment_manifest": deployment_manifest,
+                            "docker_credentials_secret": deployment_manifest_wip.get_parameter_value(
+                                "dockerCredentialsSecret",
+                                account_alias=_module.target_account,
+                                region=_module.target_region,
+                            ),
+                            "permissions_boundary_arn": _render_permissions_boundary_arn(
+                                account_id=_module.get_target_account_id(),
+                                permissions_boundary_name=deployment_manifest_wip.get_parameter_value(
+                                    "permissionsBoundaryName",
+                                    account_alias=_module.target_account,
+                                    region=_module.target_region,
+                                ),
+                            ),
                         }
                         for _module in _group.modules
                         if _module and _module.deploy_spec
@@ -178,10 +222,48 @@ def _deploy_deployment_is_dry_run(groups_to_deploy: List[ModulesManifest], deplo
     if groups_to_deploy:
         for _group in groups_to_deploy:
             for _module in _group.modules:
-                mods_would_deploy.append([deployment_name, _group.name, _module.name])
+                mods_would_deploy.append(
+                    [_module.target_account, _module.target_region, deployment_name, _group.name, _module.name]
+                )
     _print_modules(f"Modules scheduled to be deployed (created or updated): {deployment_name}", mods_would_deploy)
 
 
+def prime_target_accounts(deployment_manifest: DeploymentManifest) -> None:
+    # TODO: Investigate whether we need to validate the requested mappings against previously deployed mappings
+
+    _logger.info("Priming Accounts")
+    with concurrent.futures.ThreadPoolExecutor(max_workers=len(deployment_manifest.target_accounts_regions)) as workers:
+
+        def _prime_accounts(args: Dict[str, Any]) -> None:
+            _logger.info("Priming Acccount %s in %s", args["account_id"], args["region"])
+            commands.deploy_seedkit(**args)
+            commands.deploy_managed_policy_stack(deployment_manifest=deployment_manifest, **args)
+
+        params = [
+            {"account_id": target_account_region["account_id"], "region": target_account_region["region"]}
+            for target_account_region in deployment_manifest.target_accounts_regions
+        ]
+        _ = list(workers.map(_prime_accounts, params))
+
+
+def tear_down_target_accounts(deployment_manifest: DeploymentManifest, retain_seedkit: bool = False) -> None:
+    # TODO: Investigate whether we need to validate the requested mappings against previously deployed mappings
+    _logger.info("Tearing Down Accounts")
+    with concurrent.futures.ThreadPoolExecutor(max_workers=len(deployment_manifest.target_accounts_regions)) as workers:
+
+        def _teardown_accounts(args: Dict[str, Any]) -> None:
+            _logger.info("Tearing Down Acccount %s in %s", args["account_id"], args["region"])
+            commands.destroy_managed_policy_stack(**args)
+            if not retain_seedkit:
+                commands.destroy_seedkit(**args)
+
+        params = [
+            {"account_id": target_account_region["account_id"], "region": target_account_region["region"]}
+            for target_account_region in deployment_manifest.target_accounts_regions
+        ]
+        _ = list(workers.map(_teardown_accounts, params))
+
+
 def destroy_deployment(
     destroy_manifest: DeploymentManifest,
     remove_deploy_manifest: bool = False,
@@ -197,7 +279,7 @@ def destroy_deployment(
     deployment_manifest : DeploymentManifest
         The DeploymentManifest objec of all modules to destroy
     remove_deploy_manifest : bool, optional
-        This flag indicates whether the project resouurce policy should be deleted.
+        This flag indicates whether the project resource policy should be deleted.
         If there are ANY modules deployed, this should not be set to True.  This is only
         used when the entire deployment is destroyed
     dryrun : bool, optional
@@ -216,9 +298,6 @@ def destroy_deployment(
     print_manifest_inventory(f"Modules removed from manifest: {destroy_manifest.name}", destroy_manifest, False, "red")
 
     deployment_name = destroy_manifest.name
-    docker_credentials_secret = (
-        destroy_manifest.docker_credentials_secret if destroy_manifest.docker_credentials_secret else None
-    )
 
     print_manifest_inventory(
         f"Modules scheduled to be destroyed for: {destroy_manifest.name}", destroy_manifest, False, "red"
@@ -230,19 +309,18 @@ def destroy_deployment(
                 with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as workers:
 
                     def _exec_destroy(args: Dict[str, Any]) -> Optional[ModuleDeploymentResponse]:
-                        return _execute_destroy(
-                            args["d"],
-                            args["g"],
-                            args["m"],
-                            args["d_secret"],
-                        )
+                        return _execute_destroy(**args)
 
                     params = [
                         {
-                            "d": deployment_name,
-                            "g": _group.name,
-                            "m": _module,
-                            "d_secret": docker_credentials_secret,
+                            "group_name": _group.name,
+                            "module_manifest": _module,
+                            "deployment_manifest": destroy_manifest,
+                            "docker_credentials_secret": destroy_manifest.get_parameter_value(
+                                "dockerCredentialsSecret",
+                                account_alias=_module.target_account,
+                                region=_module.target_region,
+                            ),
                         }
                         for _module in _group.modules
                         if _module and _module.deploy_spec
@@ -257,15 +335,17 @@ def _exec_destroy(args: Dict[str, Any]) -> Optional[ModuleDeploymentResponse]:
 
         print_manifest_inventory(f"Modules Destroyed: {deployment_name}", destroy_manifest, False, "red")
         if remove_deploy_manifest:
-            remove_deployment_manifest(deployment_name)
-            remove_deployed_deployment_manifest(deployment_name)
+            session = SessionManager().get_or_create().toolchain_session
+            remove_deployment_manifest(deployment_name, session=session)
+            remove_deployed_deployment_manifest(deployment_name, session=session)
+            tear_down_target_accounts(deployment_manifest=destroy_manifest, retain_seedkit=True)
     if show_manifest:
         print_manifest_json(destroy_manifest)
 
 
 def deploy_deployment(
     deployment_manifest: DeploymentManifest,
-    deployment_params_cache: Optional[Dict[str, Any]] = None,
+    module_info_index: du.ModuleInfoIndex,
     dryrun: bool = False,
     show_manifest: bool = False,
 ) -> None:
@@ -279,8 +359,8 @@ def deploy_deployment(
     ----------
     deployment_manifest : DeploymentManifest
         The DeploymentManifest objec of all modules to deploy
-    deployment_params_cache: Dict[str,Any]
-        A dictionary representation of what is in the store (SSM for DDB) of the modules deployed
+    module_info_index:ModuleInfoIndex
+        An index of all Module Info stored in SSM across all target accounts and regions
     dryrun : bool, optional
         This flag indicates that the DeploymentManifest object should be consumed but DOES NOT
         enact any deployment changes.
@@ -292,63 +372,61 @@ def deploy_deployment(
     """
     deployment_manifest_wip = deployment_manifest.copy()
     deployment_name = deployment_manifest_wip.name
-    docker_credentials_secret = (
-        deployment_manifest_wip.docker_credentials_secret if deployment_manifest_wip.docker_credentials_secret else None
-    )
-    permission_boundary_arn = (
-        deployment_manifest_wip.permission_boundary_arn if deployment_manifest_wip.permission_boundary_arn else None
-    )
     _logger.debug("Setting up deployment for %s", deployment_name)
 
     print_manifest_inventory(
         f"Modules added to manifest: {deployment_manifest_wip.name}", deployment_manifest_wip, True
     )
-    commands.deploy_seedkit()
-    commands.deploy_managed_policy_stack(deployment_name=deployment_name, deployment_manifest=deployment_manifest_wip)
 
     groups_to_deploy = []
     unchanged_modules = []
     for group in deployment_manifest_wip.groups:
-        working_group = group.copy()
-        # TODO - Write the group manifest here...without the module deployspec
+        # working_group = group.copy()
         group_name = group.name
-        du.write_group_manifest(deployment_name=deployment_name, group_manifest=working_group)
+        # TODO: Investigate whether we need the group manifest, for now commenting
+        # du.write_group_manifest(deployment_name=deployment_name, group_manifest=working_group)
         modules_to_deploy = []
         _logger.info(" Verifying all modules in %s for deploy ", group.name)
         for module in group.modules:
-            _logger.debug("Working on --  %s", module)
+            _logger.debug("Working on -- %s", module)
             if not module.path:
-                raise Exception("Unable to parse module manifest, `path` not specified")
+                raise ValueError("Unable to parse module manifest, `path` not specified")
 
             # This checks if the modulestack file exists, else fail fast
-            _ = _get_modulestack_path(module.path)
+            # TODO: Uncomment this if it's needed, but this isn't actually doing anything
+            # _ = _get_modulestack_path(module.path)
 
             deployspec_path = _get_deployspec_path(module.path)
             with open(deployspec_path) as module_spec_file:
-                module_deploy_spec = DeploySpec(**yaml.load(module_spec_file, Loader=yaml.SafeLoader))
+                module.deploy_spec = DeploySpec(**yaml.safe_load(module_spec_file))
 
             # This MD5 is generated from the module manifest content before setting the generated values
             # of `bundle_md5` and `deploy_spec` below
             module_manifest_md5 = hashlib.md5(json.dumps(module.dict(), sort_keys=True).encode("utf-8")).hexdigest()
 
-            module.bundle_md5 = checksumdir.dirhash(os.path.join(OPS_ROOT, module.path))
+            module.bundle_md5 = checksumdir.dirhash(os.path.join(config.OPS_ROOT, module.path))
             module_deployspec_md5 = hashlib.md5(open(deployspec_path, "rb").read()).hexdigest()
 
             _build_module = du.need_to_build(
                 deployment_name=deployment_name,
                 group_name=group_name,
                 module_manifest=module,
-                module_deployspec=module_deploy_spec,
+                module_deployspec=module.deploy_spec,
                 module_deployspec_md5=module_deployspec_md5,
                 module_manifest_md5=module_manifest_md5,
                 dryrun=dryrun,
-                deployment_params_cache=deployment_params_cache,
+                deployment_params_cache=module_info_index.get_module_info(
+                    group=group_name,
+                    account_id=cast(str, module.get_target_account_id()),
+                    region=cast(str, module.target_region),
+                    module_name=module.name,
+                ),
             )
             if not _build_module:
-                module.deploy_spec = module_deploy_spec
-                unchanged_modules.append([deployment_name, group_name, module.name])
+                unchanged_modules.append(
+                    [module.target_account, module.target_region, deployment_name, group_name, module.name]
+                )
             else:
-                module.deploy_spec = module_deploy_spec
                 modules_to_deploy.append(module)
 
         if modules_to_deploy:
@@ -365,10 +443,7 @@ def deploy_deployment(
         _deploy_deployment_is_not_dry_run(
             deployment_manifest=deployment_manifest,
             deployment_manifest_wip=deployment_manifest_wip,
-            deployment_name=deployment_name,
             groups_to_deploy=groups_to_deploy,
-            permission_boundary_arn=permission_boundary_arn,
-            docker_credentials_secret=docker_credentials_secret,
         )
     else:
         _deploy_deployment_is_dry_run(groups_to_deploy=groups_to_deploy, deployment_name=deployment_name)
@@ -379,7 +454,13 @@ def deploy_deployment(
         print_manifest_json(deployment_manifest)
 
 
-def apply(deployment_spec: str, dryrun: bool = False, show_manifest: bool = False) -> None:
+def apply(
+    deployment_manifest_path: str,
+    profile: Optional[str] = None,
+    region_name: Optional[str] = None,
+    dryrun: bool = False,
+    show_manifest: bool = False,
+) -> None:
     """
     apply
         This function takes the relative path of a deployment manifest and
@@ -389,8 +470,12 @@ def apply(deployment_spec: str, dryrun: bool = False, show_manifest: bool = Fals
 
     Parameters
     ----------
-    deployment_spec : str
+    deployment_manifest_path : str
         Relative path to the deployment manifest
+    profile : str
+        If using an AWS Profile for deployment use it here
+    region_name : str
+        The name of the AWS region the deployment is based in for the toolchain
     dryrun : bool, optional
         This flag indicates that the deployment manifest should be consumed and a
         DeploymentManifest object be created (for both apply and destroy) but DOES NOT
@@ -410,14 +495,21 @@ def apply(deployment_spec: str, dryrun: bool = False, show_manifest: bool = Fals
         If the relative `path' value is a list
     """
 
-    spec_path = os.path.join(OPS_ROOT, deployment_spec)
-    with open(spec_path) as manifest_file:
+    manifest_path = os.path.join(config.OPS_ROOT, deployment_manifest_path)
+    with open(manifest_path) as manifest_file:
         deployment_manifest = DeploymentManifest(**yaml.safe_load(manifest_file))
     _logger.debug(deployment_manifest.dict())
+
+    # Initialize the SessionManager for the entire project
+    session_manager = SessionManager().get_or_create(
+        project_name=config.PROJECT, profile=profile, region_name=region_name
+    )
     if not dryrun:
-        write_deployment_manifest(deployment_manifest.name, deployment_manifest.dict())
+        write_deployment_manifest(
+            deployment_manifest.name, deployment_manifest.dict(), session=session_manager.toolchain_session
+        )
 
-    for module_group in deployment_manifest.groups if deployment_manifest.groups else []:
+    for module_group in deployment_manifest.groups:
         if module_group.path and module_group.modules:
             _logger.debug("module_group: %s", module_group)
             raise Exception("Only one of the `path` or `modules` attributes can be defined on a Group")
@@ -425,23 +517,37 @@ def apply(deployment_spec: str, dryrun: bool = False, show_manifest: bool = Fals
             _logger.debug("module_group: %s", module_group)
             raise Exception("One of the `path` or `modules` attributes must be defined on a Group")
         if module_group.path:
-            with open(os.path.join(OPS_ROOT, module_group.path)) as manifest_file:
+            with open(os.path.join(config.OPS_ROOT, module_group.path)) as manifest_file:
                 module_group.modules = [ModuleManifest(**m) for m in yaml.safe_load_all(manifest_file)]
+    deployment_manifest.validate_and_set_module_defaults()
+
+    prime_target_accounts(deployment_manifest=deployment_manifest)
+
+    module_info_index = du.populate_module_info_index(deployment_manifest=deployment_manifest)
+    destroy_manifest = du.filter_deploy_destroy(deployment_manifest, module_info_index)
 
-    deployment_params_cache = du.generate_deployment_cache(deployment_name=deployment_manifest.name)
-    destroy_manifest = du.filter_deploy_destroy(deployment_manifest, deployment_params_cache)
     destroy_deployment(
-        destroy_manifest=destroy_manifest, remove_deploy_manifest=False, dryrun=dryrun, show_manifest=show_manifest
+        destroy_manifest=destroy_manifest,
+        remove_deploy_manifest=False,
+        dryrun=dryrun,
+        show_manifest=show_manifest,
     )
     deploy_deployment(
         deployment_manifest=deployment_manifest,
-        deployment_params_cache=deployment_params_cache,
+        module_info_index=module_info_index,
         dryrun=dryrun,
         show_manifest=show_manifest,
     )
 
 
-def destroy(deployment_name: str, dryrun: bool = False, show_manifest: bool = False) -> None:
+def destroy(
+    deployment_name: str,
+    profile: Optional[str] = None,
+    region_name: Optional[str] = None,
+    dryrun: bool = False,
+    show_manifest: bool = False,
+    retain_seedkit: bool = False,
+) -> None:
     """
     destroy
         This function takes the name of a deployment and destroy all artifacts related.
@@ -449,7 +555,11 @@ def destroy(deployment_name: str, dryrun: bool = False, show_manifest: bool = Fa
     Parameters
     ----------
     deployment_name : str
-       The name of the deployment to destroy
+        The name of the deployment to destroy
+    profile : str
+        If using an AWS Profile for deployment use it here
+    region_name : str
+        The name of the AWS region the deployment is based in for the toolchain
     dryrun : bool, optional
         This flag indicates that the deployment WILL NOT
         enact any deployment changes.
@@ -461,12 +571,18 @@ def destroy(deployment_name: str, dryrun: bool = False, show_manifest: bool = Fa
         By default False
 
     """
+    project = config.PROJECT
     _logger.debug("Preparing to destroy %s", deployment_name)
-    deployment_params_cache = du.generate_deployment_cache(deployment_name=deployment_name)
-    destroy_manifest = du.generate_deployed_manifest(
-        deployment_name=deployment_name, deployment_params_cache=deployment_params_cache, skip_deploy_spec=False
-    )
+    SessionManager().get_or_create(project_name=project, profile=profile, region_name=region_name)
+    destroy_manifest = du.generate_deployed_manifest(deployment_name=deployment_name, skip_deploy_spec=False)
     if destroy_manifest:
-        destroy_deployment(destroy_manifest, remove_deploy_manifest=True, dryrun=dryrun, show_manifest=show_manifest)
+        destroy_manifest.validate_and_set_module_defaults()
+        destroy_deployment(
+            destroy_manifest,
+            remove_deploy_manifest=True,
+            dryrun=dryrun,
+            show_manifest=show_manifest,
+        )
+
     else:
         _logger.info("Deployment %s was not found, ignoring... ", deployment_name)
diff --git a/seedfarmer/commands/_module_commands.py b/seedfarmer/commands/_module_commands.py
index f5a69438..33ffe700 100644
--- a/seedfarmer/commands/_module_commands.py
+++ b/seedfarmer/commands/_module_commands.py
@@ -20,19 +20,21 @@
 from typing import Any, Dict, List, Optional, Tuple, cast
 
 import botocore.exceptions
-from aws_codeseeder import codeseeder
+from aws_codeseeder import EnvVar, codeseeder
 from aws_codeseeder.errors import CodeSeederRuntimeError
+from boto3 import Session
 
-from seedfarmer import CONFIG_FILE, OPS_ROOT, PROJECT
+from seedfarmer import config
 from seedfarmer.models.deploy_responses import CodeSeederMetadata, ModuleDeploymentResponse, StatusType
 from seedfarmer.models.manifests import DeploySpec, ModuleParameter
+from seedfarmer.services.session_manager import SessionManager
 from seedfarmer.utils import generate_hash
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
 
 def _param(key: str) -> str:
-    return f"{PROJECT.upper()}_{key}"
+    return f"{config.PROJECT.upper()}_{key}"
 
 
 def _env_vars(
@@ -42,24 +44,28 @@ def _env_vars(
     parameters: Optional[List[ModuleParameter]] = None,
     module_metadata: Optional[str] = None,
     docker_credentials_secret: Optional[str] = None,
-    permission_boundary_arn: Optional[str] = None,
+    permissions_boundary_arn: Optional[str] = None,
+    session: Optional[Session] = None,
 ) -> Dict[str, str]:
     env_vars = (
         {
-            f"{_param('PARAMETER')}_{p.upper_snake_case}": p.value if isinstance(p.value, str) else json.dumps(p.value)
+            f"{_param('PARAMETER')}_{p.upper_snake_case}": p.value
+            if isinstance(p.value, str) or isinstance(p.value, EnvVar)
+            else json.dumps(p.value)
             for p in parameters
         }
         if parameters
         else {}
     )
+    _logger.debug(f"env_vars: {env_vars}")
     env_vars[_param("DEPLOYMENT_NAME")] = deployment_name
     env_vars[_param("MODULE_METADATA")] = module_metadata if module_metadata is not None else ""
     env_vars[_param("MODULE_NAME")] = f"{group_name}-{module_manifest_name}"
-    env_vars[_param("HASH")] = generate_hash()
+    env_vars[_param("HASH")] = generate_hash(session=session)
     if docker_credentials_secret:
         env_vars["AWS_CODESEEDER_DOCKER_SECRET"] = docker_credentials_secret
-    if permission_boundary_arn:
-        env_vars[_param("PERMISSION_BOUNDARY_ARN")] = permission_boundary_arn
+    if permissions_boundary_arn:
+        env_vars[_param("PERMISSIONS_BOUNDARY_ARN")] = permissions_boundary_arn
     return env_vars
 
 
@@ -69,11 +75,13 @@ def deploy_module(
     module_path: str,
     module_deploy_spec: DeploySpec,
     module_manifest_name: str,
+    account_id: str,
+    region: str,
     parameters: Optional[List[ModuleParameter]] = None,
     module_metadata: Optional[str] = None,
     module_bundle_md5: Optional[str] = None,
     docker_credentials_secret: Optional[str] = None,
-    permission_boundary_arn: Optional[str] = None,
+    permissions_boundary_arn: Optional[str] = None,
 ) -> ModuleDeploymentResponse:
     env_vars = _env_vars(
         deployment_name=deployment_name,
@@ -82,7 +90,8 @@ def deploy_module(
         parameters=parameters,
         module_metadata=module_metadata,
         docker_credentials_secret=docker_credentials_secret,
-        permission_boundary_arn=permission_boundary_arn,
+        permissions_boundary_arn=permissions_boundary_arn,
+        session=SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region),
     )
     env_vars[_param("MODULE_MD5")] = module_bundle_md5 if module_bundle_md5 is not None else ""
 
@@ -110,6 +119,8 @@ def deploy_module(
             deployment_name=deployment_name,
             group_name=group_name,
             module_manifest_name=module_manifest_name,
+            account_id=account_id,
+            region=region,
             extra_dirs={"module": module_path},
             extra_install_commands=["cd module/"] + _phases.install.commands,
             extra_pre_build_commands=["cd module/"] + _phases.pre_build.commands,
@@ -147,6 +158,8 @@ def destroy_module(
     module_path: str,
     module_deploy_spec: DeploySpec,
     module_manifest_name: str,
+    account_id: str,
+    region: str,
     parameters: Optional[List[ModuleParameter]] = None,
     module_metadata: Optional[str] = None,
 ) -> ModuleDeploymentResponse:
@@ -156,6 +169,7 @@ def destroy_module(
         module_manifest_name=module_manifest_name,
         parameters=parameters,
         module_metadata=module_metadata,
+        session=SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region),
     )
 
     remove_ssm = [f"seedfarmer remove moduledata -d {deployment_name} -g {group_name} -m {module_manifest_name}"]
@@ -178,6 +192,8 @@ def destroy_module(
             deployment_name=deployment_name,
             group_name=group_name,
             module_manifest_name=module_manifest_name,
+            account_id=account_id,
+            region=region,
             extra_dirs={"module": module_path},
             extra_install_commands=["cd module/"] + _phases.install.commands,
             extra_pre_build_commands=["cd module/"] + _phases.pre_build.commands + export_info,
@@ -210,6 +226,8 @@ def _execute_module_commands(
     deployment_name: str,
     group_name: str,
     module_manifest_name: str,
+    account_id: str,
+    region: str,
     extra_dirs: Optional[Dict[str, Any]] = None,
     extra_install_commands: Optional[List[str]] = None,
     extra_pre_build_commands: Optional[List[str]] = None,
@@ -218,8 +236,13 @@ def _execute_module_commands(
     extra_env_vars: Optional[Dict[str, Any]] = None,
     codebuild_compute_type: Optional[str] = None,
 ) -> Tuple[str, Optional[Dict[str, str]]]:
+    session: Optional[Session] = None
+
+    if not codeseeder.EXECUTING_REMOTELY:
+        session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+
     @codeseeder.remote_function(
-        PROJECT.lower(),
+        config.PROJECT.lower(),
         extra_dirs=extra_dirs,
         extra_install_commands=extra_install_commands,
         extra_pre_build_commands=extra_pre_build_commands,
@@ -227,15 +250,21 @@ def _execute_module_commands(
         extra_post_build_commands=extra_post_build_commands,
         extra_env_vars=extra_env_vars,
         extra_exported_env_vars=[f"{_param('MODULE_METADATA')}"],
-        codebuild_role=f"{PROJECT.lower()}-{deployment_name}-{group_name}-{module_manifest_name}-{generate_hash()}",
+        codebuild_role=(
+            f"{config.PROJECT.lower()}-{deployment_name}-{group_name}"
+            f"-{module_manifest_name}-{generate_hash(session=session)}"
+        ),
         bundle_id=module_manifest_name,
         codebuild_compute_type=codebuild_compute_type,
-        extra_files={CONFIG_FILE: os.path.join(OPS_ROOT, CONFIG_FILE)},
+        extra_files={config.CONFIG_FILE: os.path.join(config.OPS_ROOT, config.CONFIG_FILE)},
+        boto3_session=session,
     )
     def _execute_module_commands(
         deployment_name: str,
         group_name: str,
         module_manifest_name: str,
+        account_id: str,
+        region: str,
         extra_dirs: Optional[Dict[str, Any]] = None,
         extra_install_commands: Optional[List[str]] = None,
         extra_pre_build_commands: Optional[List[str]] = None,
@@ -262,13 +291,8 @@ def _execute_module_commands(
                     deployment_name=deployment_name,
                     group_name=group_name,
                     module_manifest_name=module_manifest_name,
-                    extra_dirs=extra_dirs,
-                    extra_install_commands=extra_install_commands,
-                    extra_pre_build_commands=extra_pre_build_commands,
-                    extra_build_commands=extra_build_commands,
-                    extra_post_build_commands=extra_post_build_commands,
-                    extra_env_vars=extra_env_vars,
-                    codebuild_compute_type=codebuild_compute_type,
+                    account_id=account_id,
+                    region=region,
                 ),
             )
         except botocore.exceptions.ClientError as ex:
diff --git a/seedfarmer/commands/_parameter_commands.py b/seedfarmer/commands/_parameter_commands.py
index 45413eea..471d7c08 100644
--- a/seedfarmer/commands/_parameter_commands.py
+++ b/seedfarmer/commands/_parameter_commands.py
@@ -12,13 +12,16 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
-
 import logging
-from typing import Any, Dict, List, Optional, Tuple
+import os
+from typing import Any, Dict, List, Optional, Tuple, cast
+
+from aws_codeseeder import EnvVar, EnvVarType
 
-from seedfarmer import PROJECT
+from seedfarmer import config
 from seedfarmer.mgmt.module_info import get_module_metadata
-from seedfarmer.models.manifests import ModuleParameter
+from seedfarmer.models.manifests import DeploymentManifest, ModuleParameter
+from seedfarmer.services.session_manager import SessionManager
 from seedfarmer.utils import upper_snake_case
 
 _logger: logging.Logger = logging.getLogger(__name__)
@@ -27,40 +30,81 @@
 def generate_export_env_params(metadata: Optional[Dict[str, Any]]) -> Optional[List[str]]:
     envs: List[str] = []
     if metadata is not None and metadata.keys() is not None:
-        envs = [f"export {PROJECT.upper()}_PARAMETER_{upper_snake_case(k)}={metadata[k]}" for k in metadata.keys()]
+        envs = [
+            f"export {config.PROJECT.upper()}_PARAMETER_{upper_snake_case(k)}={metadata[k]}" for k in metadata.keys()
+        ]
     return envs
 
 
-def load_parameter_values(deployment_name: str, parameters: List[ModuleParameter]) -> List[ModuleParameter]:
+def load_parameter_values(
+    deployment_name: str, parameters: List[ModuleParameter], deployment_manifest: DeploymentManifest
+) -> List[ModuleParameter]:
     parameter_values = []
     parameter_values_cache: Dict[Tuple[str, str, str], Any] = {}
     for parameter in parameters:
         if _logger.isEnabledFor(logging.DEBUG):
             _logger.debug("parameter: %s", parameter.dict())
+
         if parameter.value:
             _logger.debug("static parameter value: %s", parameter.value)
             parameter_values.append(parameter)
-        # Load parameter from Module Metadata
-        elif parameter.value_from and parameter.value_from.module_metadata:
-            module_metatdata = _module_metatdata(deployment_name, parameter, parameter_values_cache)
-            parameter_values.append(module_metatdata) if module_metatdata else None
+        elif parameter.value_from:
+            if parameter.value_from.module_metadata:
+                module_metatdata = _module_metatdata(
+                    deployment_name, parameter, parameter_values_cache, deployment_manifest
+                )
+                parameter_values.append(module_metatdata) if module_metatdata else None
+            elif parameter.value_from.env_variable:
+                parameter_values.append(
+                    ModuleParameter(name=parameter.name, value=os.getenv(parameter.value_from.env_variable, ""))
+                )
+            elif parameter.value_from.parameter_store:
+                parameter_values.append(
+                    ModuleParameter(
+                        name=parameter.name,
+                        value=EnvVar(value=parameter.value_from.parameter_store, type=EnvVarType.PARAMETER_STORE),
+                    ),
+                )
+            elif parameter.value_from.secrets_manager:
+                parameter_values.append(
+                    ModuleParameter(
+                        name=parameter.name,
+                        value=EnvVar(value=parameter.value_from.secrets_manager, type=EnvVarType.SECRETS_MANAGER),
+                    ),
+                )
     return parameter_values
 
 
 def _get_param_value_cache(
-    d_name: str, g_name: str, m_name: str, parameter_values_cache: Dict[Tuple[str, str, str], Any]
+    d_name: str,
+    g_name: str,
+    m_name: str,
+    parameter_values_cache: Dict[Tuple[str, str, str], Any],
+    deployment_manifest: DeploymentManifest,
 ) -> Dict[Any, Any]:
     if (d_name, g_name, m_name) not in parameter_values_cache:
-        parameter_values_cache[(d_name, g_name, m_name)] = get_module_metadata(
-            d_name,
-            g_name,
-            m_name,
-        )
-    return parameter_values_cache[(d_name, g_name, m_name)]
+        module = deployment_manifest.get_module(group=g_name, module=m_name)
+        if module is not None:
+            module_session = (
+                SessionManager()
+                .get_or_create()
+                .get_deployment_session(
+                    account_id=cast(str, module.get_target_account_id()), region_name=cast(str, module.target_region)
+                )
+            )
+            parameter_values_cache[(d_name, g_name, m_name)] = get_module_metadata(
+                d_name, g_name, m_name, session=module_session
+            )
+        else:
+            return {}
+    return cast(Dict[Any, Any], parameter_values_cache[(d_name, g_name, m_name)])
 
 
 def _module_metatdata(
-    deployment_name: str, parameter: ModuleParameter, parameter_values_cache: Dict[Tuple[str, str, str], Any]
+    deployment_name: str,
+    parameter: ModuleParameter,
+    parameter_values_cache: Dict[Tuple[str, str, str], Any],
+    deployment_manifest: DeploymentManifest,
 ) -> Optional[ModuleParameter]:
     if parameter.value_from and parameter.value_from.module_metadata:
         group = parameter.value_from.module_metadata.group
@@ -68,7 +112,9 @@ def _module_metatdata(
         _logger.debug("Loading metadata for dependency group, module: %s, %s" % (group, module_name))
 
         # Ensure we only retrieve the SSM Parameter value once per module
-        parameter_value = _get_param_value_cache(deployment_name, group, module_name, parameter_values_cache)
+        parameter_value = _get_param_value_cache(
+            deployment_name, group, module_name, parameter_values_cache, deployment_manifest
+        )
         _logger.debug("loaded parameter value: %s", parameter_value)
 
         parameter_value = (
diff --git a/seedfarmer/commands/_stack_commands.py b/seedfarmer/commands/_stack_commands.py
index ae91d9e7..0b3ada7b 100644
--- a/seedfarmer/commands/_stack_commands.py
+++ b/seedfarmer/commands/_stack_commands.py
@@ -16,58 +16,102 @@
 import logging
 import os
 import time
-from typing import List, Optional
+from typing import List, Optional, cast
 
-from aws_codeseeder import codeseeder, commands, services
+from aws_codeseeder import EnvVar, codeseeder, commands, services
 from cfn_tools import load_yaml
 
 import seedfarmer.services._iam as iam
-from seedfarmer import OPS_ROOT, PROJECT
+from seedfarmer import config
 from seedfarmer.mgmt.module_info import _get_module_stack_names
 from seedfarmer.models.manifests import DeploymentManifest, ModuleParameter
 from seedfarmer.services._service_utils import get_account_id, get_region
+from seedfarmer.services.session_manager import SessionManager
 from seedfarmer.utils import upper_snake_case
 
-PROJECT_MANAGED_POLICY_CFN_NAME = f"{PROJECT.lower()}-managed-policy"
-PROJECT_POLICY_PATH = "resources/projectpolicy.yaml"
+_logger: logging.Logger = logging.getLogger(__name__)
 
-ACCOUNT_ID = get_account_id()
-REGION = get_region()
 
-_logger: logging.Logger = logging.getLogger(__name__)
+class StackInfo(object):
+    _PROJECT_MANAGED_POLICY_CFN_NAME: Optional[str] = None
+    PROJECT_POLICY_PATH = "resources/projectpolicy.yaml"
+
+    @property
+    def PROJECT_MANAGED_POLICY_CFN_NAME(self) -> str:
+        if self._PROJECT_MANAGED_POLICY_CFN_NAME is None:
+            self._PROJECT_MANAGED_POLICY_CFN_NAME = f"{config.PROJECT.lower()}-managed-policy"
+        return self._PROJECT_MANAGED_POLICY_CFN_NAME
+
 
+info = StackInfo()
 
-def deploy_managed_policy_stack(deployment_name: str, deployment_manifest: DeploymentManifest) -> None:
+
+def deploy_managed_policy_stack(deployment_manifest: DeploymentManifest, account_id: str, region: str) -> None:
     """
     deploy_managed_policy_stack
-        This function deployes the deployment-specific policy to allow CodeSeeder to deploy.
+        This function deploys the deployment-specific policy to allow CodeSeeder to deploy.
 
     Parameters
     ----------
-    deployment_name : str
-        The name of the deployment
     deployment_manifest : DeploymentManifest
         The DeploymentManifest object of the deploy
-
+    account_id: str
+        The Account Id where the module is deployed
+    region: str
+        The region wher
     """
     # Determine if managed policy stack already deployed
-    project_managed_policy_stack_exists, _ = services.cfn.does_stack_exist(stack_name=PROJECT_MANAGED_POLICY_CFN_NAME)
+    session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+    project_managed_policy_stack_exists, _ = services.cfn.does_stack_exist(
+        stack_name=info.PROJECT_MANAGED_POLICY_CFN_NAME, session=session
+    )
     if not project_managed_policy_stack_exists:
-        project_managed_policy_template = (
-            deployment_manifest.project_policy if deployment_manifest.project_policy else PROJECT_POLICY_PATH
+        project_managed_policy_template = cast(
+            str,
+            deployment_manifest.get_parameter_value(
+                "projectPolicy", account_id=account_id, region=region, default=info.PROJECT_POLICY_PATH
+            ),
         )
-        project_managed_policy_template = os.path.join(OPS_ROOT, project_managed_policy_template)
+        project_managed_policy_template = os.path.join(config.OPS_ROOT, project_managed_policy_template)
         if not os.path.exists(project_managed_policy_template):
             raise Exception(f"Unable to find the Project Managed Policy Template: {project_managed_policy_template}")
         _logger.debug(
-            f"Validated the existence of Project Managed Policy Template at:{project_managed_policy_template}"
+            f"Validated the existence of Project Managed Policy Template at: {project_managed_policy_template}"
         )
-        _logger.info("Deploying %s", PROJECT_MANAGED_POLICY_CFN_NAME)
+        _logger.info("Deploying %s", info.PROJECT_MANAGED_POLICY_CFN_NAME)
         services.cfn.deploy_template(
-            stack_name=PROJECT_MANAGED_POLICY_CFN_NAME,
+            stack_name=info.PROJECT_MANAGED_POLICY_CFN_NAME,
             filename=project_managed_policy_template,
-            seedkit_tag=deployment_name,
-            parameters={"ProjectName": PROJECT.lower(), "DeploymentName": deployment_name},
+            seedkit_tag=deployment_manifest.name,
+            parameters={"ProjectName": config.PROJECT.lower(), "DeploymentName": deployment_manifest.name},
+            session=session,
+        )
+
+
+def destroy_managed_policy_stack(account_id: str, region: str) -> None:
+    """
+    destroy_managed_policy_stack
+        This function destroys the deployment-specific policy.
+
+    Parameters
+    ----------
+    account_id: str
+        The Account Id where the module is deployed
+    region: str
+        The region wher
+    """
+    # Determine if managed policy stack already deployed
+    session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+    project_managed_policy_stack_exists, _ = services.cfn.does_stack_exist(
+        stack_name=info.PROJECT_MANAGED_POLICY_CFN_NAME, session=session
+    )
+    if project_managed_policy_stack_exists:
+        _logger.info(
+            "Destroying Stack %s in Account/Region: %s/%s", info.PROJECT_MANAGED_POLICY_CFN_NAME, account_id, region
+        )
+        services.cfn.destroy_stack(
+            stack_name=info.PROJECT_MANAGED_POLICY_CFN_NAME,
+            session=session,
         )
 
 
@@ -75,6 +119,8 @@ def destroy_module_stack(
     deployment_name: str,
     group_name: str,
     module_name: str,
+    account_id: str,
+    region: str,
     docker_credentials_secret: Optional[str] = None,
 ) -> None:
     """
@@ -87,12 +133,21 @@ def destroy_module_stack(
         The name of the deployment
     group : str
         The name of the group
-    module : str
+    module_name : str
         The name of the module
+    account_id: str
+        The Account Id where the module is deployed
+    region: str
+        The region where the module is deployed
     """
-    module_stack_name, module_role_name = _get_module_stack_names(deployment_name, group_name, module_name)
+    session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+    module_stack_name, module_role_name = _get_module_stack_names(
+        deployment_name, group_name, module_name, session=session
+    )
     # Detach the Project Policy
-    seedkit_stack_exists, seedkit_stack_name, stack_outputs = commands.seedkit_deployed(seedkit_name=PROJECT)
+    seedkit_stack_exists, seedkit_stack_name, stack_outputs = commands.seedkit_deployed(
+        seedkit_name=config.PROJECT, session=session
+    )
 
     policies_arn = []
     if seedkit_stack_exists:
@@ -102,7 +157,7 @@ def destroy_module_stack(
 
     # Extract Project Managed policy name
     project_managed_policy_stack_exists, stack_outputs = services.cfn.does_stack_exist(
-        stack_name=PROJECT_MANAGED_POLICY_CFN_NAME
+        stack_name=info.PROJECT_MANAGED_POLICY_CFN_NAME, session=session
     )
     if project_managed_policy_stack_exists:
         project_managed_policy_arn = stack_outputs.get("ProjectPolicyARN")
@@ -114,14 +169,16 @@ def destroy_module_stack(
     _logger.debug("module_role_name %s", module_role_name)
 
     for policy_arn in policies_arn:
-        iam.detach_policy_from_role(role_name=module_role_name, policy_arn=policy_arn)
+        iam.detach_policy_from_role(role_name=module_role_name, policy_arn=policy_arn, session=session)
 
     if not codeseeder.EXECUTING_REMOTELY:
-        services.cfn.destroy_stack(module_stack_name)
+        services.cfn.destroy_stack(module_stack_name, session=session)
 
     if docker_credentials_secret:
-        iam.detach_inline_policy_from_role(role_name=module_role_name, policy_name=docker_credentials_secret)
-    iam.delete_role(role_name=module_role_name)
+        iam.detach_inline_policy_from_role(
+            role_name=module_role_name, policy_name=docker_credentials_secret, session=session
+        )
+    iam.delete_role(role_name=module_role_name, session=session)
 
 
 def deploy_module_stack(
@@ -129,9 +186,11 @@ def deploy_module_stack(
     deployment_name: str,
     group_name: str,
     module_name: str,
+    account_id: str,
+    region: str,
     parameters: List[ModuleParameter],
     docker_credentials_secret: Optional[str] = None,
-    permission_boundary_arn: Optional[str] = None,
+    permissions_boundary_arn: Optional[str] = None,
 ) -> None:
     """
     deploy_module_stack
@@ -150,16 +209,23 @@ def deploy_module_stack(
         The name of the module
     parameters : List[ModuleParameter]
         A ModuleParameter object with any necessary parameters from other deployed modules
+    account_id: str
+        The Account Id where the module is deployed
+    region: str
+        The region where the module is deployed
     docker_credentials_secret: str
         OPTIONAL parameter with name of SecrestManager of docker credentials
-    permission_boundary_arn: str
-        OPTIONAL parameter with ARN of PermissionBoundary ManagedPolicy
+    permissions_boundary_arn: str
+        OPTIONAL parameter with Name of PermissionBoundary ManagedPolicy
     """
 
     if module_stack_path:
         _logger.debug(module_stack_path)
 
-    module_stack_name, module_role_name = _get_module_stack_names(deployment_name, group_name, module_name)
+    session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+    module_stack_name, module_role_name = _get_module_stack_names(
+        deployment_name, group_name, module_name, session=session
+    )
 
     # Create IAM Role
     trust_policy = {
@@ -169,7 +235,7 @@ def deploy_module_stack(
         ],
     }
 
-    iam.create_check_iam_role(trust_policy, module_role_name, permission_boundary_arn)
+    iam.create_check_iam_role(trust_policy, module_role_name, permissions_boundary_arn, session=session)
 
     group_module_name = f"{group_name}-{module_name}"
 
@@ -194,6 +260,8 @@ def deploy_module_stack(
                     stack_parameters[k] = value
                 elif isinstance(value, list):
                     stack_parameters[k] = ",".join(value)
+                elif isinstance(value, EnvVar):
+                    stack_parameters[k] = value.value
                 else:
                     json.dumps(value)
         _logger.debug("stack_parameters: %s", stack_parameters)
@@ -205,18 +273,21 @@ def deploy_module_stack(
             filename=module_stack_path,
             seedkit_tag=module_stack_name,
             parameters=stack_parameters,
+            session=session,
         )
 
     # Attaching managed IAM Policies
     _logger.debug("Extracting the Codeseeder Managed policy")
-    seedkit_stack_exists, seedkit_stack_name, stack_outputs = commands.seedkit_deployed(seedkit_name=PROJECT)
+    seedkit_stack_exists, seedkit_stack_name, stack_outputs = commands.seedkit_deployed(
+        seedkit_name=config.PROJECT, session=session
+    )
     if seedkit_stack_exists:
         _logger.debug("Seedkit stack exists - %s", seedkit_stack_name)
         seedkit_managed_policy_arn = stack_outputs.get("SeedkitResourcesPolicyArn")
 
     # Extract Project Managed policy name
     project_managed_policy_stack_exists, stack_outputs = services.cfn.does_stack_exist(
-        stack_name=PROJECT_MANAGED_POLICY_CFN_NAME
+        stack_name=info.PROJECT_MANAGED_POLICY_CFN_NAME, session=session
     )
 
     _logger.debug("project_managed_policy_output is : %s", stack_outputs)
@@ -227,7 +298,7 @@ def deploy_module_stack(
         raise ValueError("Project Managed Stack is missing the export `ProjectPolicyARN`")
 
     policies = [seedkit_managed_policy_arn, project_managed_policy_arn]
-    policies_attached = iam.attach_policy_to_role(module_role_name, policies)
+    policies_attached = iam.attach_policy_to_role(module_role_name, policies, session=session)
     if policies.sort() == policies_attached.sort():
         _logger.info("Delaying module %s deployment to allow IAM Roles and Policies to take effect", group_module_name)
         time.sleep(10)  # on first deployment roles and policy attachments need time to take effect
@@ -244,7 +315,10 @@ def deploy_module_stack(
                         "secretsmanager:DescribeSecret",
                         "secretsmanager:ListSecretVersionIds",
                     ],
-                    "Resource": f"arn:aws:secretsmanager:{REGION}:{ACCOUNT_ID}:secret:{docker_credentials_secret}*",
+                    "Resource": (
+                        f"arn:aws:secretsmanager:{get_region(session=session)}:{get_account_id(session=session)}"
+                        f":secret:{docker_credentials_secret}*"
+                    ),
                 },
                 {"Effect": "Allow", "Action": ["secretsmanager:ListSecrets"], "Resource": "*"},
             ],
@@ -253,17 +327,42 @@ def deploy_module_stack(
 
     if docker_credentials_secret:
         iam.attach_inline_policy(
-            role_name=module_role_name,
-            policy_body=policy_body,
-            policy_name=docker_credentials_secret,
+            role_name=module_role_name, policy_body=policy_body, policy_name=docker_credentials_secret, session=session
         )
 
 
-def deploy_seedkit() -> None:
+def deploy_seedkit(account_id: str, region: str) -> None:
     """
     deploy_seedkit
         Accessor method to CodeSeeder to deploy the SeedKit if not deployed
+
+    Parameters
+    ----------
+    account_id: str
+        The Account Id where the module is deployed
+    region: str
+        The region wher"""
+    session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+    stack_exists, _, stack_outputs = commands.seedkit_deployed(seedkit_name=config.PROJECT, session=session)
+    deploy_codeartifact = "CodeArtifactRepository" in stack_outputs
+    if stack_exists:
+        _logger.debug("Updating SeedKit for Account/Region: %s/%s", account_id, region)
+    else:
+        _logger.debug("Initializing SeedKit for Account/Region: %s/%s", account_id, region)
+    commands.deploy_seedkit(seedkit_name=config.PROJECT, deploy_codeartifact=deploy_codeartifact, session=session)
+
+
+def destroy_seedkit(account_id: str, region: str) -> None:
     """
-    stack_exists, _, _ = commands.seedkit_deployed(seedkit_name=PROJECT)
-    if not stack_exists:
-        commands.deploy_seedkit(seedkit_name=PROJECT)
+    destroy_seedkit
+        Accessor method to CodeSeeder to destroy the SeedKit if deployed
+
+    Parameters
+    ----------
+    account_id: str
+        The Account Id where the module is deployed
+    region: str
+        The region wher"""
+    session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
+    _logger.debug("Destroying SeedKit for Account/Region: %s/%s", account_id, region)
+    commands.destroy_seedkit(seedkit_name=config.PROJECT, session=session)
diff --git a/seedfarmer/mgmt/deploy_utils.py b/seedfarmer/mgmt/deploy_utils.py
index 2b4cf7d3..34df2961 100644
--- a/seedfarmer/mgmt/deploy_utils.py
+++ b/seedfarmer/mgmt/deploy_utils.py
@@ -12,34 +12,109 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
+import concurrent.futures
 import logging
-from typing import Any, Dict, List, Optional
+from threading import Lock
+from typing import Any, Dict, List, Optional, Set, Tuple, cast
 
 import yaml
+from boto3 import Session
 
 import seedfarmer.mgmt.module_info as mi
 from seedfarmer.models.manifests import DeploymentManifest, DeploySpec, ModuleManifest, ModulesManifest
+from seedfarmer.services.session_manager import SessionManager
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
 
-def generate_deployment_cache(deployment_name: str) -> Optional[Dict[str, Any]]:
+class ModuleInfoIndex(object):
+    def __init__(self) -> None:
+        super().__init__()
+        self._index: Dict[Tuple[str, str, str, str], Dict[str, Any]] = dict()
+        self._groups: Set[str] = set()
+        self._groups_idx: Dict[str, Set[Tuple[str, str, str, str]]] = dict()
+        self._module_names_idx: Dict[Tuple[str, str], Tuple[str, str, str, str]] = dict()
+        self._lock = Lock()
+
+    @property
+    def groups(self) -> Set[str]:
+        return self._groups
+
+    def get_module_info(
+        self, *, group: str, account_id: str, region: str, module_name: str
+    ) -> Optional[Dict[str, Any]]:
+        return self._index.get((group, account_id, region, module_name))
+
+    def index_module_info(
+        self, *, group: str, account_id: str, region: str, module_name: str, module_info: Dict[str, Any]
+    ) -> None:
+        with self._lock:
+            module_info_key = (group, account_id, region, module_name)
+            current_module_info = self._index.get(module_info_key, {})
+            self._index[module_info_key] = {**current_module_info, **module_info}
+            self._groups.add(group)
+            current_group_keys = self._groups_idx.get(group, set())
+            current_group_keys.add(module_info_key)
+            self._groups_idx[group] = current_group_keys
+            self._module_names_idx[(group, module_name)] = module_info_key
+
+    def get_keys_for_group(self, group: str) -> List[Dict[str, str]]:
+        return [
+            {"group": m[0], "account_id": m[1], "region": m[2], "module_name": m[3]}
+            for m in self._groups_idx.get(group, [])
+        ]
+
+    def get_key_for_module_name(self, group: str, module_name: str) -> Dict[str, str]:
+        m = self._module_names_idx.get((group, module_name), None)
+        if m is not None:
+            return {"group": m[0], "account_id": m[1], "region": m[2], "module_name": m[3]}
+        else:
+            return {"group": "", "account_id": "", "region": "", "module_name": ""}
+
+
+def populate_module_info_index(deployment_manifest: DeploymentManifest) -> ModuleInfoIndex:
     """
-    generate_deployment_cache
-        Fetch all parameters for the deployemnt currently stored
+    populate_module_info_index
+        Fetch all info for the deployemnt currently stored, across all Target accounts and regions
 
     Parameters
     ----------
-    deployment_name: str
-        The name of the deployment
+    deployment_manifest: DeploymentManifest
+        The DeploymentManifest, including TargetAccount and Region mappings
 
     Returns
     -------
-    Dict[str,Any]
-        A dictionary representation of what is in the store (SSM for DDB) of the modules deployed
+    ModuleInfoIndex
+        An index of Module info for all Target accounts and regions
     """
-    c = mi.get_parameter_data_cache(deployment=deployment_name)
-    return c if len(c) > 0 else None
+    module_info_index = ModuleInfoIndex()
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=len(deployment_manifest.target_accounts_regions)) as workers:
+
+        def _get_module_info(args: Dict[str, Any]) -> None:
+            session = (
+                SessionManager()
+                .get_or_create()
+                .get_deployment_session(account_id=args["account_id"], region_name=args["region"])
+            )
+            module_info = mi.get_parameter_data_cache(deployment=deployment_manifest.name, session=session)
+            for key, value in module_info.items():
+                key_parts = key.split("/")[1:]
+                if len(key_parts) < 4:
+                    continue
+                group = key_parts[2]
+                module_name = key_parts[3]
+                module_info_index.index_module_info(
+                    group=group, module_name=module_name, module_info={key: value}, **args
+                )
+
+        params = [
+            {"account_id": target_account_region["account_id"], "region": target_account_region["region"]}
+            for target_account_region in deployment_manifest.target_accounts_regions
+        ]
+        _ = list(workers.map(_get_module_info, params))
+
+    return module_info_index
 
 
 def write_deployed_deployment_manifest(deployment_manifest: DeploymentManifest) -> None:
@@ -50,17 +125,17 @@ def write_deployed_deployment_manifest(deployment_manifest: DeploymentManifest)
     Parameters
     ----------
     deployment_manifest : DeploymentManifest
-       The deployment manifest ojject to store
+        The deployment manifest ojject to store
     """
     deployment_name = deployment_manifest.name
     for group in deployment_manifest.groups:
         delattr(group, "modules")
-    mi.write_deployed_deployment_manifest(deployment=deployment_name, data=deployment_manifest.dict())
+    session = SessionManager().get_or_create().toolchain_session
+    mi.write_deployed_deployment_manifest(deployment=deployment_name, data=deployment_manifest.dict(), session=session)
 
 
 def generate_deployed_manifest(
     deployment_name: str,
-    deployment_params_cache: Optional[Dict[str, Any]] = None,
     skip_deploy_spec: bool = False,
 ) -> Optional[DeploymentManifest]:
     """
@@ -78,22 +153,27 @@ def generate_deployed_manifest(
     Optional[DeploymentManifest]
         The hydrated DeploymentManifest object of deployed modules
     """
-    dep_manifest_dict = mi.get_deployed_deployment_manifest(deployment_name, deployment_params_cache)
+    session_manager = SessionManager().get_or_create()
+    dep_manifest_dict = mi.get_deployed_deployment_manifest(deployment_name, session=session_manager.toolchain_session)
     if dep_manifest_dict is None:
         # No successful deployments, just use what was last requested
-        dep_manifest_dict = mi.get_deployment_manifest(deployment_name, deployment_params_cache)
-    destroy_manifest = None
+        dep_manifest_dict = mi.get_deployment_manifest(deployment_name, session=session_manager.toolchain_session)
+    deployed_manifest = None
     if dep_manifest_dict:
+        deployed_manifest = DeploymentManifest(**dep_manifest_dict)
+        module_info_index = populate_module_info_index(deployment_manifest=deployed_manifest)
         for module_group in dep_manifest_dict["groups"] if dep_manifest_dict["groups"] else []:
+            # TODO: Investigate whether this group manifest in SSM is needed, for now load from index
+            # from_ssm = mi.get_group_manifest(deployment_name, group_name, module_info_index.get_keys_for_group())
             group_name = module_group["name"]
-            from_ssm = mi.get_group_manifest(deployment_name, group_name, deployment_params_cache)
-            if from_ssm:
-                module_group["modules"] = from_ssm["modules"]
-                if not skip_deploy_spec:
-                    for module in module_group["modules"] if module_group["modules"] else []:
-                        module["deploy_spec"] = mi.get_deployspec(deployment_name, group_name, module["name"])
-        destroy_manifest = DeploymentManifest(**dep_manifest_dict)
-    return destroy_manifest
+            module_group["modules"] = _populate_group_modules_from_index(
+                deployment_name=deployment_name,
+                group_name=group_name,
+                module_info_index=module_info_index,
+                skip_deploy_spec=skip_deploy_spec,
+            )
+        deployed_manifest.groups = [ModulesManifest(**group) for group in dep_manifest_dict["groups"]]
+    return deployed_manifest
 
 
 def need_to_build(
@@ -137,23 +217,43 @@ def need_to_build(
     d = deployment_name
     g = group_name
     m = module_manifest.name if module_manifest.name else ""
-    module_bundle_md5 = module_manifest.bundle_md5 if module_manifest.bundle_md5 else []
-
+    module_bundle_md5 = module_manifest.bundle_md5 if module_manifest.bundle_md5 else ""
+    session = (
+        SessionManager()
+        .get_or_create()
+        .get_deployment_session(
+            account_id=cast(str, module_manifest.get_target_account_id()),
+            region_name=cast(str, module_manifest.target_region),
+        )
+    )
     if (
-        mi.does_md5_match(d, g, m, str(module_bundle_md5), mi.ModuleConst.BUNDLE, deployment_params_cache)
-        and mi.does_md5_match(d, g, m, module_deployspec_md5, mi.ModuleConst.DEPLOYSPEC, deployment_params_cache)
-        and mi.does_md5_match(d, g, m, module_manifest_md5, mi.ModuleConst.MANIFEST, deployment_params_cache)
+        mi.does_md5_match(
+            d, g, m, str(module_bundle_md5), mi.ModuleConst.BUNDLE, deployment_params_cache, session=session
+        )
+        and mi.does_md5_match(
+            d, g, m, module_deployspec_md5, mi.ModuleConst.DEPLOYSPEC, deployment_params_cache, session=session
+        )
+        and mi.does_md5_match(
+            d, g, m, module_manifest_md5, mi.ModuleConst.MANIFEST, deployment_params_cache, session=session
+        )
     ):
         return False
     else:
         if not dryrun:
-            mi.write_deployspec(deployment=d, group=g, module=m, data=module_deployspec.dict())
-            mi.write_module_manifest(deployment=d, group=g, module=m, data=module_manifest.dict())
+            mi.write_deployspec(deployment=d, group=g, module=m, data=module_deployspec.dict(), session=session)
+            mi.write_module_manifest(deployment=d, group=g, module=m, data=module_manifest.dict(), session=session)
             mi.write_module_md5(
-                deployment=d, group=g, module=m, hash=module_deployspec_md5, type=mi.ModuleConst.DEPLOYSPEC
+                deployment=d,
+                group=g,
+                module=m,
+                hash=module_deployspec_md5,
+                type=mi.ModuleConst.DEPLOYSPEC,
+                session=session,
             )
-            mi.write_module_md5(deployment=d, group=g, module=m, hash=module_manifest_md5, type=mi.ModuleConst.MANIFEST)
-            mi.remove_module_md5(deployment=d, group=g, module=m, type=mi.ModuleConst.BUNDLE)
+            mi.write_module_md5(
+                deployment=d, group=g, module=m, hash=module_manifest_md5, type=mi.ModuleConst.MANIFEST, session=session
+            )
+            mi.remove_module_md5(deployment=d, group=g, module=m, type=mi.ModuleConst.BUNDLE, session=session)
         return True
 
 
@@ -173,9 +273,7 @@ def write_group_manifest(deployment_name: str, group_manifest: ModulesManifest)
     mi.write_group_manifest(deployment=deployment_name, group=g, data=group_manifest.dict())
 
 
-def filter_deploy_destroy(
-    apply_manifest: DeploymentManifest, deployment_params_cache: Optional[Dict[str, Any]] = None
-) -> DeploymentManifest:
+def filter_deploy_destroy(apply_manifest: DeploymentManifest, module_info_index: ModuleInfoIndex) -> DeploymentManifest:
     """
     This method takes a populated DeploymentManifest object based off the requested deployment.
     It compares this requested deployment with what is currently deployed.  If there are groups or
@@ -186,74 +284,97 @@ def filter_deploy_destroy(
     ----------
     apply_manifest : DeploymentManifest
         The DeploymentManifest object based off a deployment manifest of a requested deployment.
+    module_info_index: ModuleInfoIndex
+        The index of existing module info for all Target accounts and regions
 
     Returns
     -------
     DeploymentManifest
         A populated DeploymentManifest object with the modules needed to be destroyed
-    """ """"""
-    dep_name = apply_manifest.name
+    """
+    deployment_name = apply_manifest.name
 
     destroy_manifest = apply_manifest.copy()
     delattr(destroy_manifest, "groups")
-    destroy_group_list = _populate_groups_to_remove(dep_name, apply_manifest.groups)
+    destroy_group_list = _populate_groups_to_remove(deployment_name, apply_manifest.groups, module_info_index)
     destroy_manifest.groups = destroy_group_list
     for group in apply_manifest.groups:
-        destroy_module_list = _populate_modules_to_remove(dep_name, group.name, group.modules, deployment_params_cache)
+        destroy_module_list = _populate_modules_to_remove(deployment_name, group.name, group.modules, module_info_index)
         if destroy_module_list:
             to_destroy = ModulesManifest(name=group.name, path=group.path, modules=destroy_module_list)
             destroy_manifest.groups.append(to_destroy)
+    destroy_manifest.validate_and_set_module_defaults()
     return destroy_manifest
 
 
+def _populate_group_modules_from_index(
+    deployment_name: str, group_name: str, module_info_index: ModuleInfoIndex, skip_deploy_spec: bool
+) -> List[Dict[str, Any]]:
+    modules = []
+    for group_key in module_info_index.get_keys_for_group(group_name):
+        deployment_params_cache = module_info_index.get_module_info(**group_key)
+        module_manifest = mi.get_module_manifest(
+            deployment_name, group_name, group_key["module_name"], deployment_params_cache
+        )
+        if module_manifest is not None:
+            if not skip_deploy_spec:
+                module_manifest["deploy_spec"] = mi.get_deployspec(
+                    deployment_name, group_name, group_key["module_name"], deployment_params_cache
+                )
+            modules.append(module_manifest)
+    return modules
+
+
 def _populate_groups_to_remove(
-    dep_name: str, apply_groups: List[ModulesManifest], deployment_params_cache: Optional[Dict[str, Any]] = None
+    deployment_name: str, apply_groups: List[ModulesManifest], module_info_index: ModuleInfoIndex
 ) -> List[ModulesManifest]:
     set_requested_groups = set([g.name for g in apply_groups])
-    set_deployed_groups = set(mi.get_all_groups(dep_name))
+    set_deployed_groups = module_info_index.groups
     destroy_groups = list(sorted(set_deployed_groups - set_requested_groups))
     _logger.debug("Groups already deployed that will be DESTROYED : %s", destroy_groups)
     destroy_group_list = []
     for destroy_group in destroy_groups:
-        group_manifests = mi.get_group_manifest(dep_name, destroy_group, deployment_params_cache)
-        if group_manifests:
-            valid_modules = []
-            for module in group_manifests["modules"]:
-                mod_manifest = mi.get_module_manifest(dep_name, destroy_group, module["name"], deployment_params_cache)
-                if mod_manifest:
-                    module["deploy_spec"] = mi.get_deployspec(
-                        dep_name, destroy_group, module["name"], deployment_params_cache
-                    )
-                    module["parameters"] = mod_manifest["parameters"]
-                    valid_modules.append(module)
-            if valid_modules:
-                group_manifests["modules"] = valid_modules
-            destroy_group_list.append(ModulesManifest(**group_manifests))
+        # TODO: Investigate whether it's necessary to read the existing group manifest from SSM
+        # group_manifest = mi.get_group_manifest(deployment_name, destroy_group, deployment_params_cache)
+        group_manifest = {
+            "name": destroy_group,
+            "modules": _populate_group_modules_from_index(
+                deployment_name=deployment_name,
+                group_name=destroy_group,
+                module_info_index=module_info_index,
+                skip_deploy_spec=False,
+            ),
+        }
+        destroy_group_list.append(ModulesManifest(**group_manifest))
     return destroy_group_list
 
 
 def _populate_modules_to_remove(
-    dep_name: str,
-    group: str,
-    apply_modules: List[ModuleManifest],
-    deployment_params_cache: Optional[Dict[str, Any]] = None,
+    deployment_name: str, group: str, apply_modules: List[ModuleManifest], module_info_index: ModuleInfoIndex
 ) -> List[ModuleManifest]:
     set_requests_modules = set([m.name for m in apply_modules])
-    set_deployed_modules = set(mi.get_deployed_modules(dep_name, group, deployment_params_cache))
+    set_deployed_modules = set([m["module_name"] for m in module_info_index.get_keys_for_group(group)])
     destroy_modules = list(sorted(set_deployed_modules - set_requests_modules))
     _logger.debug("Modules of Group %s already deployed that will be DESTROYED: %s", group, destroy_modules)
 
     destroy_module_list = []
     for destroy_module in destroy_modules:
-        mod_manifest = mi.get_module_manifest(dep_name, group, destroy_module, deployment_params_cache)
-        if mod_manifest:
-            mod_manifest["deploy_spec"] = mi.get_deployspec(dep_name, group, destroy_module, deployment_params_cache)
-            destroy_module_list.append(ModuleManifest(**mod_manifest))
+        deployment_params_cache = module_info_index.get_module_info(
+            **module_info_index.get_key_for_module_name(group=group, module_name=destroy_module)
+        )
+        module_manifest = mi.get_module_manifest(deployment_name, group, destroy_module, deployment_params_cache)
+        if module_manifest is not None:
+            module_manifest["deploy_spec"] = mi.get_deployspec(
+                deployment_name, group, destroy_module, deployment_params_cache
+            )
+            destroy_module_list.append(ModuleManifest(**module_manifest))
     return destroy_module_list
 
 
-def update_deployspec(deployment: str, group: str, module: str, module_path: str) -> None:
+def update_deployspec(
+    deployment: str, group: str, module: str, module_path: str, session: Optional[Session] = None
+) -> None:
     d_path = mi._get_deployspec_path(module_path=module_path)
     with open(d_path) as deploymentspec:
         new_spec = DeploySpec(**yaml.safe_load(deploymentspec))
-    mi.write_deployspec(deployment, group, module, new_spec.dict())
+    mi.write_deployspec(deployment, group, module, new_spec.dict(), session=session)
diff --git a/seedfarmer/mgmt/module_info.py b/seedfarmer/mgmt/module_info.py
index 9cb7e47b..d8d1fd89 100644
--- a/seedfarmer/mgmt/module_info.py
+++ b/seedfarmer/mgmt/module_info.py
@@ -17,7 +17,9 @@
 from enum import Enum
 from typing import Any, Dict, List, Optional, Tuple
 
-from seedfarmer import OPS_ROOT, PROJECT
+from boto3 import Session
+
+from seedfarmer import config
 from seedfarmer.services import _secrets_manager as secrets
 from seedfarmer.services import _ssm as store
 from seedfarmer.utils import generate_hash
@@ -34,7 +36,7 @@ class ModuleConst(Enum):
     DEPLOYED = "deployed"
 
 
-def get_parameter_data_cache(deployment: str) -> Dict[str, Any]:
+def get_parameter_data_cache(deployment: str, session: Session) -> Dict[str, Any]:
     """
     get_parameter_data_cache
         Fetch the deployment parameters stored
@@ -42,30 +44,36 @@ def get_parameter_data_cache(deployment: str) -> Dict[str, Any]:
     Parameters
     ----------
     deployment : str
-       Name of the deployment
+        Name of the deployment
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Dict[str,Any]
         A dictionary representation of what is in the store (SSM for DDB) of the modules deployed
     """
-    return store.get_all_parameter_data_by_path(_deployment_key(deployment))
+    return store.get_all_parameter_data_by_path(prefix=_deployment_key(deployment), session=session)
 
 
-def get_all_deployments() -> List[str]:
+def get_all_deployments(session: Optional[Session] = None) -> List[str]:
     """
     get_all_deployments
         Get all names of curently deployments
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     List[str]
         A list of the deployments in the account
     """
-    prefix = f"/{PROJECT}"
+    prefix = f"/{config.PROJECT}"
     _filter = f"{ModuleConst.MANIFEST.value}"
     ret = set()
-    params = store.list_parameters_with_filter(prefix, _filter)
+    params = store.list_parameters_with_filter(prefix=prefix, contains_string=_filter, session=session)
     for param in params:
         _logger.debug(param)
         p = param.split("/")[3]
@@ -74,7 +82,9 @@ def get_all_deployments() -> List[str]:
     return list(ret)
 
 
-def get_all_groups(deployment: str, params_cache: Optional[Dict[str, Any]] = None) -> List[str]:
+def get_all_groups(
+    deployment: str, params_cache: Optional[Dict[str, Any]] = None, session: Optional[Session] = None
+) -> List[str]:
     """
     get_all_groups
         Get all groups in a deployment
@@ -82,9 +92,11 @@ def get_all_groups(deployment: str, params_cache: Optional[Dict[str, Any]] = Non
     Parameters
     ----------
     deployment : str
-       The name of the deployment
+        The name of the deployment
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
@@ -94,7 +106,11 @@ def get_all_groups(deployment: str, params_cache: Optional[Dict[str, Any]] = Non
     prefix = _deployment_key(deployment)
     _filter = f"{ModuleConst.MANIFEST.value}"
     ret = set()
-    params = params_cache.keys() if params_cache else store.list_parameters_with_filter(prefix, _filter)
+    params = (
+        params_cache.keys()
+        if params_cache
+        else store.list_parameters_with_filter(prefix=prefix, contains_string=_filter, session=session)
+    )
     for param in params:
         p = param.split("/")[3]
         if ModuleConst.MANIFEST.value not in p:
@@ -102,7 +118,9 @@ def get_all_groups(deployment: str, params_cache: Optional[Dict[str, Any]] = Non
     return list(ret)
 
 
-def get_deployed_modules(deployment: str, group: str, params_cache: Optional[Dict[str, Any]] = None) -> List[str]:
+def get_deployed_modules(
+    deployment: str, group: str, params_cache: Optional[Dict[str, Any]] = None, session: Optional[Session] = None
+) -> List[str]:
     """
     get_deployed_modules
         Get all modules deployed in a group of a deployment
@@ -114,23 +132,29 @@ def get_deployed_modules(deployment: str, group: str, params_cache: Optional[Dic
     group : str
         The name of the group
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     List[str]
         A list of the names of the modules in the group
     """
-    prefix = f"/{PROJECT}/{deployment}/{group}"
+    prefix = f"/{config.PROJECT}/{deployment}/{group}"
     _filter = f"{ModuleConst.MD5.value}/{ModuleConst.BUNDLE.value}"
-    params = params_cache.keys() if params_cache else store.list_parameters_with_filter(prefix, _filter)
+    params = (
+        params_cache.keys() if params_cache else store.list_parameters_with_filter(prefix, _filter, session=session)
+    )
     ret: List[str] = []
     for param in params:
         ret.append(param.split("/")[4]) if _filter in param else None
     return ret
 
 
-def get_module_md5(deployment: str, group: str, module: str, type: ModuleConst) -> Optional[str]:
+def get_module_md5(
+    deployment: str, group: str, module: str, type: ModuleConst, session: Optional[Session] = None
+) -> Optional[str]:
     """
     get_module_md5
         Get the md5 of a currently deployed module.
@@ -149,6 +173,8 @@ def get_module_md5(deployment: str, group: str, module: str, type: ModuleConst)
         The name of the module
     type : ModuleConst
         An emumeration value of the md5 of the module you want
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
@@ -156,12 +182,16 @@ def get_module_md5(deployment: str, group: str, module: str, type: ModuleConst)
         The md5 hash as a string
     """
     name = _md5_module_key(deployment, group, module, type)
-    p = store.get_parameter_if_exists(name=name)
+    p = store.get_parameter_if_exists(name=name, session=session)
     return p["hash"] if p else None
 
 
 def get_module_metadata(
-    deployment: str, group: str, module: str, params_cache: Optional[Dict[str, Any]] = None
+    deployment: str,
+    group: str,
+    module: str,
+    params_cache: Optional[Dict[str, Any]] = None,
+    session: Optional[Session] = None,
 ) -> Optional[Dict[str, Any]]:
     """
     get_module_metadata
@@ -176,18 +206,24 @@ def get_module_metadata(
     module : str
         The name of the module
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Optional[Dict[str, Any]]
         A dict containg the metadata of the module requested
     """
-    return _fetch_helper(_metadata_key(deployment, group, module), params_cache)
+    return _fetch_helper(_metadata_key(deployment, group, module), params_cache, session=session)
 
 
 def get_module_manifest(
-    deployment: str, group: str, module: str, params_cache: Optional[Dict[str, Any]] = None
+    deployment: str,
+    group: str,
+    module: str,
+    params_cache: Optional[Dict[str, Any]] = None,
+    session: Optional[Session] = None,
 ) -> Optional[Dict[str, Any]]:
     """
     get_module_manifest
@@ -202,18 +238,24 @@ def get_module_manifest(
     module : str
         The name of the module
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Optional[Dict[str, Any]]
         A dict containg the manifest of the module requested
     """
-    return _fetch_helper(_manifest_key(deployment, group, module), params_cache)
+    return _fetch_helper(_manifest_key(deployment, group, module), params_cache, session=session)
 
 
 def get_deployspec(
-    deployment: str, group: str, module: str, params_cache: Optional[Dict[str, Any]] = None
+    deployment: str,
+    group: str,
+    module: str,
+    params_cache: Optional[Dict[str, Any]] = None,
+    session: Optional[Session] = None,
 ) -> Optional[Dict[str, Any]]:
     """
     get_deployspec
@@ -228,17 +270,21 @@ def get_deployspec(
     module : str
         The name of the module
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Optional[Dict[str, Any]]
         A dict containg the deployspec of the module requested
     """
-    return _fetch_helper(_deployspec_key(deployment, group, module), params_cache)
+    return _fetch_helper(_deployspec_key(deployment, group, module), params_cache, session=session)
 
 
-def get_deployment_manifest(deployment: str, params_cache: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
+def get_deployment_manifest(
+    deployment: str, params_cache: Optional[Dict[str, Any]] = None, session: Optional[Session] = None
+) -> Optional[Dict[str, Any]]:
     """
     get_deployment_manifest
         Get the deployment manifest stored for a deployment
@@ -248,18 +294,20 @@ def get_deployment_manifest(deployment: str, params_cache: Optional[Dict[str, An
     deployment : str
         The name of the deployment
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Optional[Dict[str, Any]]
         A dict of the deployment
     """
-    return _fetch_helper(_deployment_manifest_key(deployment), params_cache)
+    return _fetch_helper(_deployment_manifest_key(deployment), params_cache, session=session)
 
 
 def get_deployed_deployment_manifest(
-    deployment: str, params_cache: Optional[Dict[str, Any]] = None
+    deployment: str, params_cache: Optional[Dict[str, Any]] = None, session: Optional[Session] = None
 ) -> Optional[Dict[str, Any]]:
     """
     get_deployed_deployment_manifest
@@ -270,17 +318,19 @@ def get_deployed_deployment_manifest(
     deployment : str
         The name of the deployment
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Optional[Dict[str, Any]]
         A dict of the deployment
     """
-    return _fetch_helper(_deployed_deployment_manifest_key(deployment), params_cache)
+    return _fetch_helper(_deployed_deployment_manifest_key(deployment), params_cache, session=session)
 
 
-def get_secret_secrets_manager(name: str) -> Dict[str, Any]:
+def get_secret_secrets_manager(name: str, session: Optional[Session] = None) -> Dict[str, Any]:
     """
     get_secret_secrets_manager Fetches the data in the Secrets Manager with the key (name) given
 
@@ -288,17 +338,19 @@ def get_secret_secrets_manager(name: str) -> Dict[str, Any]:
     ----------
     name : str
         The name of the Secret in the Secrets manager
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Dict[str, Any]
         The object in the Secrets Manager
     """
-    return secrets.get_secret_secrets_manager(name=name)
+    return secrets.get_secret_secrets_manager(name=name, session=session)
 
 
 def get_group_manifest(
-    deployment: str, group: str, params_cache: Optional[Dict[str, Any]] = None
+    deployment: str, group: str, params_cache: Optional[Dict[str, Any]] = None, session: Optional[Session] = None
 ) -> Optional[Dict[str, Any]]:
     """
     get_group_manifest
@@ -311,14 +363,16 @@ def get_group_manifest(
     group : str
         The name of the group
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
     Optional[Dict[str, Any]]
         A dict of the group in a deployment
     """
-    return _fetch_helper(_group_key(deployment, group), params_cache)
+    return _fetch_helper(_group_key(deployment, group), params_cache, session=session)
 
 
 def does_md5_match(
@@ -328,6 +382,7 @@ def does_md5_match(
     hash: str,
     type: ModuleConst,
     deployment_params_cache: Optional[Dict[str, Any]] = None,
+    session: Optional[Session] = None,
 ) -> bool:
     """
     does_md5_match
@@ -346,7 +401,9 @@ def does_md5_match(
     type : ModuleConst
         An emumeration value of the md5 of the module you want
     params_cache : Dict[str,Any], optional
-       A populated dict with the key  of the parameter stored and its value
+        A populated dict with the key  of the parameter stored and its value
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
 
     Returns
     -------
@@ -355,7 +412,7 @@ def does_md5_match(
     """
     name = _md5_module_key(deployment, group, module, type)
     if not deployment_params_cache:
-        p = store.get_parameter_if_exists(name=name)
+        p = store.get_parameter_if_exists(name=name, session=session)
     else:
         p = deployment_params_cache[name] if name in deployment_params_cache.keys() else None
     if not p:
@@ -366,7 +423,7 @@ def does_md5_match(
         return True
 
 
-def does_module_exist(deployment: str, group: str, module: str) -> bool:
+def does_module_exist(deployment: str, group: str, module: str, session: Optional[Session] = None) -> bool:
     """
     does_module_exist
         Checks if a module of a group is deployed
@@ -379,15 +436,22 @@ def does_module_exist(deployment: str, group: str, module: str) -> bool:
         The name of the group
     module : str
         The name of the module
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
+
     Returns
     -------
     bool
         Whether the module is deployed
     """
-    return store.does_parameter_exist(name=_md5_module_key(deployment, group, module, ModuleConst.BUNDLE))
+    return store.does_parameter_exist(
+        name=_md5_module_key(deployment, group, module, ModuleConst.BUNDLE), session=session
+    )
 
 
-def write_metadata(deployment: str, group: str, module: str, data: Dict[str, Any]) -> None:
+def write_metadata(
+    deployment: str, group: str, module: str, data: Dict[str, Any], session: Optional[Session] = None
+) -> None:
     """
     write_metadata
         Persists the medadata of a deployed module
@@ -402,14 +466,16 @@ def write_metadata(deployment: str, group: str, module: str, data: Dict[str, Any
         The name of the module
     data : Dict[str, Any]
         The metadata of the module
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.put_parameter(name=_metadata_key(deployment, group, module), obj=data)
+    store.put_parameter(name=_metadata_key(deployment, group, module), obj=data, session=session)
 
 
-def write_group_manifest(deployment: str, group: str, data: Dict[str, Any]) -> None:
+def write_group_manifest(deployment: str, group: str, data: Dict[str, Any], session: Optional[Session] = None) -> None:
     """
     write_group_manifest
-         Persists the manifest of a deployed group
+        Persists the manifest of a deployed group
 
     Parameters
     ----------
@@ -419,11 +485,15 @@ def write_group_manifest(deployment: str, group: str, data: Dict[str, Any]) -> N
         The name of the group
     data : Dict[str, Any]
         The metadat of the module
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.put_parameter(name=_group_key(deployment, group), obj=data)
+    store.put_parameter(name=_group_key(deployment, group), obj=data, session=session)
 
 
-def write_module_manifest(deployment: str, group: str, module: str, data: Dict[str, Any]) -> None:
+def write_module_manifest(
+    deployment: str, group: str, module: str, data: Dict[str, Any], session: Optional[Session] = None
+) -> None:
     """
     write_module_manifest
         Persists the manifest of a deployed module
@@ -438,11 +508,15 @@ def write_module_manifest(deployment: str, group: str, module: str, data: Dict[s
         The name of the module
     data : Dict[str, Any]
         A dict of the data to be persisted
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.put_parameter(name=_manifest_key(deployment, group, module), obj=data)
+    store.put_parameter(name=_manifest_key(deployment, group, module), obj=data, session=session)
 
 
-def write_deployspec(deployment: str, group: str, module: str, data: Dict[str, Any]) -> None:
+def write_deployspec(
+    deployment: str, group: str, module: str, data: Dict[str, Any], session: Optional[Session] = None
+) -> None:
     """
     write_deployspec
         Persists the deployspec of a deployed module
@@ -457,11 +531,15 @@ def write_deployspec(deployment: str, group: str, module: str, data: Dict[str, A
         The name of the module
     data : Dict[str, Any]
         A dict of the data to be persisted
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.put_parameter(name=_deployspec_key(deployment, group, module), obj=data)
+    store.put_parameter(name=_deployspec_key(deployment, group, module), obj=data, session=session)
 
 
-def write_module_md5(deployment: str, group: str, module: str, hash: str, type: ModuleConst) -> None:
+def write_module_md5(
+    deployment: str, group: str, module: str, hash: str, type: ModuleConst, session: Optional[Session] = None
+) -> None:
     """
     write_module_md5
         Persists the md5 of a module.
@@ -482,11 +560,13 @@ def write_module_md5(deployment: str, group: str, module: str, hash: str, type:
         The md5 has of the data
     type : ModuleConst
         An emumeration value of the md5 of the module this is
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.put_parameter(name=_md5_module_key(deployment, group, module, type), obj={"hash": hash})
+    store.put_parameter(name=_md5_module_key(deployment, group, module, type), obj={"hash": hash}, session=session)
 
 
-def write_deployment_manifest(deployment: str, data: Dict[str, Any]) -> None:
+def write_deployment_manifest(deployment: str, data: Dict[str, Any], session: Optional[Session] = None) -> None:
     """
     write_deployment
         Persists the deployment manifest
@@ -497,13 +577,17 @@ def write_deployment_manifest(deployment: str, data: Dict[str, Any]) -> None:
         The name of the deployment
     data : Dict[str, Any]
         A dict of the deployment manifest
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
     if _logger.isEnabledFor(logging.DEBUG):
         _logger.debug("Writing to %s values %s", _deployment_manifest_key(deployment), data)
-    store.put_parameter(name=_deployment_manifest_key(deployment), obj=data)
+    store.put_parameter(name=_deployment_manifest_key(deployment), obj=data, session=session)
 
 
-def write_deployed_deployment_manifest(deployment: str, data: Dict[str, Any]) -> None:
+def write_deployed_deployment_manifest(
+    deployment: str, data: Dict[str, Any], session: Optional[Session] = None
+) -> None:
     """
     write_deployed_deployment_manifest
         Persists the deployment manifest once all modules have been deployed
@@ -514,14 +598,16 @@ def write_deployed_deployment_manifest(deployment: str, data: Dict[str, Any]) ->
         The name of the deployment
     data : Dict[str, Any]
         A dict of the deployment manifest
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
     key = _deployed_deployment_manifest_key(deployment)
     _logger.debug("Writing to %s value %s", key, data)
 
-    store.put_parameter(name=key, obj=data)
+    store.put_parameter(name=key, obj=data, session=session)
 
 
-def remove_module_info(deployment: str, group: str, module: str) -> None:
+def remove_module_info(deployment: str, group: str, module: str, session: Optional[Session] = None) -> None:
     """
     remove_module_info
         Delete all persisted data of a module in a group of a deployment
@@ -534,11 +620,13 @@ def remove_module_info(deployment: str, group: str, module: str) -> None:
         The name of the group
     module : str
         The name of the module
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.delete_parameters(parameters=_all_module_keys(deployment, group, module))
+    store.delete_parameters(parameters=_all_module_keys(deployment, group, module), session=session)
 
 
-def remove_group_info(deployment: str, group: str) -> None:
+def remove_group_info(deployment: str, group: str, session: Optional[Session] = None) -> None:
     """
     remove_group_info
         Delete all persisted data of a group of a deployment
@@ -549,11 +637,15 @@ def remove_group_info(deployment: str, group: str) -> None:
         The name of the deployment
     group : str
         The name of the group
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.delete_parameters(parameters=(_all_group_keys(deployment, group)))
+    store.delete_parameters(parameters=(_all_group_keys(deployment, group)), session=session)
 
 
-def remove_module_md5(deployment: str, group: str, module: str, type: ModuleConst) -> None:
+def remove_module_md5(
+    deployment: str, group: str, module: str, type: ModuleConst, session: Optional[Session] = None
+) -> None:
     """
     remove_module_md5
         Delete the md5 hash persisted of a module.
@@ -571,12 +663,14 @@ def remove_module_md5(deployment: str, group: str, module: str, type: ModuleCons
     module : str
         The name of the module
     type : ModuleConst
-         An emumeration value of the md5 you want to delete
+        An emumeration value of the md5 you want to delete
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.delete_parameters(parameters=[_md5_module_key(deployment, group, module, type)])
+    store.delete_parameters(parameters=[_md5_module_key(deployment, group, module, type)], session=session)
 
 
-def remove_deployment_manifest(deployment: str) -> None:
+def remove_deployment_manifest(deployment: str, session: Optional[Session] = None) -> None:
     """
     remove_deployment_manifest
         Delete the deployment manifest persisted of a deployment
@@ -585,11 +679,13 @@ def remove_deployment_manifest(deployment: str) -> None:
     ----------
     deployment : str
         The name of the deployment
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.delete_parameters(parameters=[_deployment_manifest_key(deployment)])
+    store.delete_parameters(parameters=[_deployment_manifest_key(deployment)], session=session)
 
 
-def remove_deployed_deployment_manifest(deployment: str) -> None:
+def remove_deployed_deployment_manifest(deployment: str, session: Optional[Session] = None) -> None:
     """
     remove_deployment_manifest
         Delete the deployment manifest persisted of a deployment
@@ -598,40 +694,42 @@ def remove_deployed_deployment_manifest(deployment: str) -> None:
     ----------
     deployment : str
         The name of the deployment
+    session: Session, optional
+        The boto3.Session to use to for SSM Parameter queries, default None
     """
-    store.delete_parameters(parameters=[_deployed_deployment_manifest_key(deployment)])
+    store.delete_parameters(parameters=[_deployed_deployment_manifest_key(deployment)], session=session)
 
 
 def _metadata_key(deployment: str, group: str, module: str) -> str:
-    return f"/{PROJECT}/{deployment}/{group}/{module}/{ModuleConst.METADATA.value}"
+    return f"/{config.PROJECT}/{deployment}/{group}/{module}/{ModuleConst.METADATA.value}"
 
 
 def _md5_module_key(deployment: str, group: str, module: str, type: ModuleConst) -> str:
-    return f"/{PROJECT}/{deployment}/{group}/{module}/{ModuleConst.MD5.value}/{type.value}"
+    return f"/{config.PROJECT}/{deployment}/{group}/{module}/{ModuleConst.MD5.value}/{type.value}"
 
 
 def _md5_group_key(deployment: str, group: str, type: ModuleConst) -> str:
-    return f"/{PROJECT}/{deployment}/{group}/{ModuleConst.MD5.value}/{type.value}"
+    return f"/{config.PROJECT}/{deployment}/{group}/{ModuleConst.MD5.value}/{type.value}"
 
 
 def _deployspec_key(deployment: str, group: str, module: str) -> str:
-    return f"/{PROJECT}/{deployment}/{group}/{module}/{ModuleConst.DEPLOYSPEC.value}"
+    return f"/{config.PROJECT}/{deployment}/{group}/{module}/{ModuleConst.DEPLOYSPEC.value}"
 
 
 def _manifest_key(deployment: str, group: str, module: str) -> str:
-    return f"/{PROJECT}/{deployment}/{group}/{module}/{ModuleConst.MANIFEST.value}"
+    return f"/{config.PROJECT}/{deployment}/{group}/{module}/{ModuleConst.MANIFEST.value}"
 
 
 def _group_key(deployment: str, group: str) -> str:
-    return f"/{PROJECT}/{deployment}/{group}/{ModuleConst.MANIFEST.value}"
+    return f"/{config.PROJECT}/{deployment}/{group}/{ModuleConst.MANIFEST.value}"
 
 
 def _deployment_manifest_key(deployment: str) -> str:
-    return f"/{PROJECT}/{deployment}/{ModuleConst.MANIFEST.value}"
+    return f"/{config.PROJECT}/{deployment}/{ModuleConst.MANIFEST.value}"
 
 
 def _deployed_deployment_manifest_key(deployment: str) -> str:
-    return f"/{PROJECT}/{deployment}/{ModuleConst.MANIFEST.value}/{ModuleConst.DEPLOYED.value}"
+    return f"/{config.PROJECT}/{deployment}/{ModuleConst.MANIFEST.value}/{ModuleConst.DEPLOYED.value}"
 
 
 def _all_module_keys(deployment: str, group: str, module: str) -> List[str]:
@@ -650,24 +748,28 @@ def _all_group_keys(deployment: str, group: str) -> List[str]:
 
 
 def _deployment_key(deployment: str) -> str:
-    return f"/{PROJECT}/{deployment}/"
+    return f"/{config.PROJECT}/{deployment}/"
 
 
-def _fetch_helper(name: str, params_cache: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
+def _fetch_helper(
+    name: str, params_cache: Optional[Dict[str, Any]] = None, session: Optional[Session] = None
+) -> Optional[Dict[str, Any]]:
     if params_cache:
-        return params_cache[name] if name in params_cache.keys() else None
+        return params_cache.get(name, None)
     else:
-        return store.get_parameter_if_exists(name=name)
+        return store.get_parameter_if_exists(name=name, session=session)
 
 
-def _get_module_stack_names(deployment_name: str, group_name: str, module_name: str) -> Tuple[str, str]:
-    module_stack_name = f"{PROJECT}-{deployment_name}-{group_name}-{module_name}-iam-policy"
-    module_role_name = f"{PROJECT}-{deployment_name}-{group_name}-{module_name}-{generate_hash()}"
+def _get_module_stack_names(
+    deployment_name: str, group_name: str, module_name: str, session: Optional[Session] = None
+) -> Tuple[str, str]:
+    module_stack_name = f"{config.PROJECT}-{deployment_name}-{group_name}-{module_name}-iam-policy"
+    module_role_name = f"{config.PROJECT}-{deployment_name}-{group_name}-{module_name}-{generate_hash(session=session)}"
     return module_stack_name, module_role_name
 
 
 def _get_modulestack_path(module_path: str) -> Any:
-    p = os.path.join(OPS_ROOT, module_path, "modulestack.yaml")
+    p = os.path.join(config.OPS_ROOT, module_path, "modulestack.yaml")
     if not os.path.exists(p):
         _logger.debug("No modulestack.yaml found")
         return None
@@ -675,7 +777,7 @@ def _get_modulestack_path(module_path: str) -> Any:
 
 
 def _get_deployspec_path(module_path: str) -> str:
-    p = os.path.join(OPS_ROOT, module_path, "deployspec.yaml")
+    p = os.path.join(config.OPS_ROOT, module_path, "deployspec.yaml")
     if not os.path.exists(p):
         raise Exception("No deployspec.yaml file found in module directory: %s", p)
-    return os.path.join(OPS_ROOT, module_path, "deployspec.yaml")
+    return os.path.join(config.OPS_ROOT, module_path, "deployspec.yaml")
diff --git a/seedfarmer/mgmt/module_init.py b/seedfarmer/mgmt/module_init.py
index 55bc4ee3..2ad74490 100644
--- a/seedfarmer/mgmt/module_init.py
+++ b/seedfarmer/mgmt/module_init.py
@@ -14,11 +14,11 @@
 
 import logging
 import os
-from typing import Optional, cast
+from typing import Optional
 
 from cookiecutter.main import cookiecutter
 
-from seedfarmer import CONFIG_FILE, OPS_ROOT, PROJECT
+from seedfarmer import config
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
@@ -37,9 +37,9 @@ def create_module_dir(module_name: str, group_name: Optional[str], template_url:
     template_url : Optional[List[str]]
         A URL, for example a Github repo, that is or contains templating for the initialization
     """
-    module_root = os.path.join(OPS_ROOT, "modules")
+    module_root = os.path.join(config.OPS_ROOT, "modules")
     module_path = os.path.join(module_root, module_name)
-    output_dir = cast(str, module_root)
+    output_dir = module_root
 
     if group_name:
         module_path = os.path.join(module_root, group_name, module_name)
@@ -78,12 +78,15 @@ def create_project(template_url: Optional[str]) -> None:
     """
 
     checkout_branch = "init-project" if template_url == "https://github.com/awslabs/seed-farmer.git" else None
-    _logger.info(" New project will be created in the following dir: %s", os.path.join(OPS_ROOT, PROJECT))
+    _logger.info(" New project will be created in the following dir: %s", os.path.join(config.OPS_ROOT, config.PROJECT))
     cookiecutter(
         template=template_url,
         checkout=checkout_branch,
         no_input=True,
-        extra_context={"project_name": PROJECT},
-        output_dir=OPS_ROOT,
+        extra_context={"project_name": config.PROJECT},
+        output_dir=config.OPS_ROOT,
+    )
+    os.replace(
+        os.path.join(config.OPS_ROOT, config.CONFIG_FILE),
+        os.path.join(config.OPS_ROOT, config.PROJECT, config.CONFIG_FILE),
     )
-    os.replace(os.path.join(OPS_ROOT, CONFIG_FILE), os.path.join(OPS_ROOT, PROJECT, CONFIG_FILE))
diff --git a/seedfarmer/models/manifests.py b/seedfarmer/models/manifests.py
index e2c29ed3..abdb123e 100644
--- a/seedfarmer/models/manifests.py
+++ b/seedfarmer/models/manifests.py
@@ -12,8 +12,9 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
+import os
 from enum import Enum
-from typing import Any, List, Optional
+from typing import Any, Dict, List, Optional, Tuple, Union, cast
 
 from pydantic import PrivateAttr
 
@@ -33,7 +34,7 @@ class BuildPhase(CamelModel):
     BuildPhase
     This is a list of strings that are passed to CodeSeeder to be executed
     in their respective phases as commands
-    """ """"""
+    """
 
     commands: List[str] = []
 
@@ -43,7 +44,7 @@ class BuildPhases(CamelModel):
     BuildPhases
     This object has the individual commands for each of the define build phases:
     install, pre_build,  build, post_build
-    """ """"""
+    """
 
     install: BuildPhase = BuildPhase()
     pre_build: BuildPhase = BuildPhase()
@@ -56,7 +57,7 @@ class ExecutionType(CamelModel):
     ExecutionType
     This an object that contains the Build Phases object for the destroy or deploy
     object of the DeploySpec
-    """ """"""
+    """
 
     phases: BuildPhases = BuildPhases()
 
@@ -67,7 +68,7 @@ class DeploySpec(CamelModel):
     This represents the commands passed to CodeSeeder that will be executed
     on behalf of the module to be built.
     The deploy and the destroy objects each have an ExecutionType object.
-    """ """"""
+    """
 
     deploy: Optional[ExecutionType] = None
     destroy: Optional[ExecutionType] = None
@@ -90,14 +91,20 @@ class ModuleRef(CamelModel):
 
 class ValueRef(CamelModel):
     module_metadata: Optional[ModuleRef] = None
+    env_variable: Optional[str] = None
+    parameter_store: Optional[str] = None
+    secrets_manager: Optional[str] = None
 
 
-class ModuleParameter(CamelModel):
+class ValueFromRef(CamelModel):
+    value_from: Optional[ValueRef] = None
+
+
+class ModuleParameter(ValueFromRef):
     _upper_snake_case: str = PrivateAttr()
 
     name: str
     value: Optional[Any] = None
-    value_from: Optional[ValueRef] = None
 
     def __init__(self, **data: Any) -> None:
         super().__init__(**data)
@@ -114,13 +121,22 @@ class ModuleManifest(CamelModel):
     This is a module in a group of the deployment and consists of a name,
     the path of the module manifest, any ModuleParameters for deployment, and
     the DeploySpec.
-    """ """"""
+    """
 
     name: str
     path: str
     bundle_md5: Optional[str]
     parameters: List[ModuleParameter] = []
-    deploy_spec: Optional[DeploySpec]
+    deploy_spec: Optional[DeploySpec] = None
+    target_account: Optional[str] = None
+    target_region: Optional[str] = None
+    _target_account_id: Optional[str] = PrivateAttr(default=None)
+
+    def set_target_account_id(self, account_id: str) -> None:
+        self._target_account_id = account_id
+
+    def get_target_account_id(self) -> Optional[str]:
+        return self._target_account_id
 
 
 class ModulesManifest(CamelModel):
@@ -128,7 +144,7 @@ class ModulesManifest(CamelModel):
     ModulesManifest
     This is a group in the deployment and defines the name of the group,
     the path of the group definiton, and the modules that comprise the group
-    """ """"""
+    """
 
     name: str
     path: Optional[str] = None
@@ -136,17 +152,183 @@ class ModulesManifest(CamelModel):
     concurrency: Optional[int] = None
 
 
+class RegionMapping(CamelModel):
+    """
+    RegionMapping
+    This class provides metadata about the regions where Modules are deployed
+    """
+
+    region: str
+    default: bool = False
+    parameters_regional: Dict[str, str] = {}
+
+
+class TargetAccountMapping(CamelModel):
+    """
+    TargetAccountMapping
+    This class provides metadata about the accounts where Modules are deployed
+    """
+
+    alias: str
+    account_id: Union[str, ValueFromRef]
+    default: bool = False
+    parameters_global: Dict[str, str] = {}
+    region_mappings: List[RegionMapping] = []
+    _default_region: Optional[RegionMapping] = PrivateAttr(default=None)
+    _region_index: Dict[str, RegionMapping] = PrivateAttr(default_factory=dict)
+
+    def __init__(self, **kwargs: Any) -> None:
+        super().__init__(**kwargs)
+        for r in self.region_mappings:
+            if r.default:
+                self._default_region = r
+            self._region_index[r.region] = r
+
+    def get_region_mapping(self, region: str) -> Optional[RegionMapping]:
+        return self._region_index.get(region, None)
+
+    @property
+    def actual_account_id(self) -> str:
+        if isinstance(self.account_id, str):
+            return self.account_id
+        elif isinstance(self.account_id, ValueFromRef):
+            if self.account_id.value_from and self.account_id.value_from.module_metadata is not None:
+                raise ValueError("Loading value from Module Metadata is not supported in the Deployment Manifest")
+            elif self.account_id.value_from and self.account_id.value_from.env_variable:
+                return os.getenv(self.account_id.value_from.env_variable, "")
+            else:
+                raise ValueError("Unsupported valueFrom type")
+        else:
+            raise ValueError("Unsupported accountId type")
+
+    @property
+    def default_region_mapping(self) -> Optional[RegionMapping]:
+        return self._default_region
+
+
 class DeploymentManifest(CamelModel):
     """
     DeploymentManifest
     This represents the top layer of the Deployment definiton.
     It includes things like the name of the deployment, the groups in the deployment
     and a policy that is applied to all build roles.
-    """ """"""
+    """
 
     name: str
+    toolchainRegion: str
     groups: List[ModulesManifest] = []
-    project_policy: Optional[str]
     description: Optional[str]
-    docker_credentials_secret: Optional[str]
-    permission_boundary_arn: Optional[str]
+    target_account_mappings: List[TargetAccountMapping] = []
+    _default_account: Optional[TargetAccountMapping] = PrivateAttr(default=None)
+    _account_alias_index: Dict[str, TargetAccountMapping] = PrivateAttr(default_factory=dict)
+    _account_id_index: Dict[str, TargetAccountMapping] = PrivateAttr(default_factory=dict)
+    _accounts_regions: Optional[List[Dict[str, str]]] = PrivateAttr(default=None)
+    _module_index: Dict[Tuple[str, str], ModuleManifest] = PrivateAttr(default_factory=dict)
+
+    def __init__(self, **kwargs: Any) -> None:
+        super().__init__(**kwargs)
+        for ta in self.target_account_mappings:
+            if ta.default:
+                self._default_account = ta
+            self._account_alias_index[ta.alias] = ta
+            self._account_id_index[ta.actual_account_id] = ta
+
+    def get_target_account_mapping(
+        self, account_alias: Optional[str] = None, account_id: Optional[str] = None
+    ) -> Optional[TargetAccountMapping]:
+        if account_alias is None and account_id is None:
+            raise ValueError("One of 'account_alias' or 'account_id' is required")
+        elif account_alias is not None and account_id is not None:
+            raise ValueError("Only one of 'account_alias' and 'account_id' is allowed")
+        elif account_alias:
+            return self._account_alias_index.get(account_alias, None)
+        elif account_id:
+            return self._account_id_index.get(account_id, None)
+        else:
+            return None
+
+    @property
+    def default_target_account_mapping(self) -> Optional[TargetAccountMapping]:
+        return self._default_account
+
+    @property
+    def target_accounts_regions(self) -> List[Dict[str, str]]:
+        if self._accounts_regions is None:
+            self._accounts_regions = []
+            for target_account in self.target_account_mappings:
+                for region in target_account.region_mappings:
+                    self._accounts_regions.append(
+                        {
+                            "alias": target_account.alias,
+                            "account_id": target_account.actual_account_id,
+                            "region": region.region,
+                        }
+                    )
+        return self._accounts_regions
+
+    def get_parameter_value(
+        self,
+        parameter: str,
+        *,
+        account_alias: Optional[str] = None,
+        account_id: Optional[str] = None,
+        region: Optional[str] = None,
+        default: Optional[str] = None,
+    ) -> Optional[str]:
+        if account_alias is not None and account_id is not None:
+            raise ValueError("Only one of 'account_alias' and 'account_id' is allowed")
+
+        use_default_account = account_alias is None and account_id is None
+        use_default_region = region is None
+
+        for target_account in self.target_account_mappings:
+            if (
+                account_alias == target_account.alias
+                or account_id == target_account.actual_account_id
+                or (use_default_account and target_account.default)
+            ):
+                # Search the region_mappings for the region, if the parameter is in parameters_regional return it
+                for region_mapping in target_account.region_mappings:
+                    if (
+                        region == region_mapping.region or (use_default_region and region_mapping.default)
+                    ) and parameter in region_mapping.parameters_regional:
+                        return region_mapping.parameters_regional[parameter]
+
+                # If no region_mapping found for the region or no value for the parameter found in parameters_regional
+                # return the value for parameter from parameters_global, default None
+                return target_account.parameters_global.get(parameter, default)
+        else:
+            return default
+
+    def validate_and_set_module_defaults(self) -> None:
+        for group in self.groups:
+            for module in group.modules:
+                self._module_index[(group.name, module.name)] = module
+                module.target_account = (
+                    self.default_target_account_mapping.alias
+                    if self.default_target_account_mapping is not None and module.target_account is None
+                    else module.target_account
+                )
+
+                target_account = self.get_target_account_mapping(account_alias=module.target_account)
+                if target_account is None:
+                    raise ValueError(
+                        f"Invalid target_account ({module.target_account}) for "
+                        f"Module {module.name} in Group {group.name}"
+                    )
+                module.set_target_account_id(target_account.actual_account_id)
+
+                module.target_region = (
+                    target_account.default_region_mapping.region
+                    if target_account.default_region_mapping is not None and module.target_region is None
+                    else module.target_region
+                )
+
+                if target_account.get_region_mapping(region=cast(str, module.target_region)) is None:
+                    raise ValueError(
+                        f"Invalid target_region ({module.target_region}) in target_account ({target_account.alias}) "
+                        f"for Module {module.name} in Group {group.name}"
+                    )
+
+    def get_module(self, group: str, module: str) -> Optional[ModuleManifest]:
+        return self._module_index.get((group, module), None)
diff --git a/seedfarmer/output_utils.py b/seedfarmer/output_utils.py
index fbbadd9d..d52dd151 100644
--- a/seedfarmer/output_utils.py
+++ b/seedfarmer/output_utils.py
@@ -35,7 +35,7 @@ def print_deployment_inventory(description: str, dep: List[str], color: str = "y
         A list of strings of the deployment names
     color : str, optional
         The color of the Title , by default "yellow"
-    """ """"""
+    """
     table = Table(title=f"[bold {color}]{description}", title_justify="left")
 
     table.add_column("Deployemnt", justify="left", style="cyan", no_wrap=True)
@@ -61,9 +61,11 @@ def print_manifest_inventory(
        Show the relative path of the module code, by default False
     color : str, optional
        The color of the Title, by default "yellow"
-    """ """"""
+    """
     table = Table(title=f"[bold {color}]{description}", title_justify="left")
 
+    table.add_column("Account", justify="left", style="yellow", no_wrap=True)
+    table.add_column("Region", justify="left", style="white", no_wrap=True)
     table.add_column("Deployemnt", justify="left", style="cyan", no_wrap=True)
     table.add_column("Group", justify="left", style="magenta")
     table.add_column("Module", justify="left", style="green")
@@ -74,10 +76,12 @@ def print_manifest_inventory(
         for module in group.modules:
             m_name = module.name
             m_path = module.path
+            region = module.target_region
+            account = module.target_account
             if show_path:
-                table.add_row(d_name, g_name, m_name, m_path)
+                table.add_row(account, region, d_name, g_name, m_name, m_path)
             else:
-                table.add_row(d_name, g_name, m_name)
+                table.add_row(account, region, d_name, g_name, m_name)
 
     console.print(table)
 
@@ -85,11 +89,13 @@ def print_manifest_inventory(
 def _print_modules(description: str, modules_list: List[Any]) -> None:
     table = Table(title=f"[bold yellow]{description}", title_justify="left")
 
+    table.add_column("Account", justify="left", style="yellow", no_wrap=True)
+    table.add_column("Region", justify="left", style="white", no_wrap=True)
     table.add_column("Deployemnt", style="cyan", no_wrap=True)
     table.add_column("Group", justify="left", style="magenta")
     table.add_column("Module", justify="left", style="green")
     for lst in modules_list:
-        table.add_row(lst[0], lst[1], lst[2])
+        table.add_row(lst[0], lst[1], lst[2], lst[3], lst[4])
 
     console.print(table)
 
@@ -102,11 +108,11 @@ def print_manifest_json(dep: DeploymentManifest) -> None:
     ----------
     dep : DeploymentManifest
         The DeploymentManifest to be printed as json
-    """ """"""
+    """
     console.print(dep.dict(), overflow="ignore", crop=False)
 
 
-def print_json(payload: Optional[Dict]) -> None:
+def print_json(payload: Optional[Dict[str, Any]]) -> None:
     """
     Pretty-print to console a json representation of the DeploymentManifest
 
@@ -114,7 +120,7 @@ def print_json(payload: Optional[Dict]) -> None:
     ----------
     dep : DeploymentManifest
         The DeploymentManifest to be printed as json
-    """ """"""
+    """
     console.print(payload, overflow="ignore", crop=False)
 
 
@@ -128,7 +134,7 @@ def print_bolded(message: str, color: str = "yellow") -> None:
         The String
     color : str, optional
        The color you want the message printed in.... default is "yellow"
-    """ """"""
+    """
     console.print(f"[bold {color}]{message}")
 
 
diff --git a/seedfarmer/resources/deployment_role.template b/seedfarmer/resources/deployment_role.template
new file mode 100644
index 00000000..579e247d
--- /dev/null
+++ b/seedfarmer/resources/deployment_role.template
@@ -0,0 +1,151 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: AWS CloudFormation for the SeedFarmer Deployment Role
+Outputs:
+  DeploymentRoleName:
+    Value:
+      Ref: DeploymentRole
+Resources:
+  DeploymentRole:
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        -   Action: sts:AssumeRole
+            Effect: Allow
+            Principal:
+                AWS: arn:aws:iam::{{ toolchain_account_id }}:role/seedfarmer-{{ project_name }}-toolchain-role
+      Path: /
+      Policies:
+        - PolicyName: InlineToolchain
+          PolicyDocument:
+            Statement:
+            - Action:
+              - kms:Get*
+              - kms:Decrypt
+              - kms:Untag*
+              - kms:Put*
+              - kms:List*
+              - kms:Tag*
+              - kms:Encrypt
+              - kms:Describe*
+              - kms:Update*
+              - kms:Delete*
+              - kms:Create*
+              Effect: Allow
+              Resource:
+                - Fn::Sub: "arn:aws:kms:*:${AWS::AccountId}:alias/codeseeder-{{ project_name }}-*"
+              Sid: DeploymentKMSAlias
+            - Action:
+              - kms:ListKeys
+              - kms:UpdateCustomKeyStore
+              - kms:ListAliases
+              - kms:CreateKey
+              - kms:PutKeyPolicy
+              - kms:Tag*
+              - kms:Untag*
+              - kms:ScheduleKey*
+              - kms:DescribeKey
+              - kms:CreateAlias
+              - kms:DeleteAlias
+              Effect: Allow
+              Resource: '*'
+              Sid: DeploymentKMSKey
+            - Action:
+              - iam:Delete*
+              - iam:Create*
+              - iam:Get*
+              - iam:Tag*
+              - iam:Untag*
+              - iam:Update*
+              - iam:AttachRolePolicy
+              - iam:PutRolePolicy
+              - iam:Pass*
+              - iam:DetachRolePolicy
+              - iam:List*
+              Effect: Allow
+              Resource:
+                - Fn::Sub: "arn:aws:iam::${AWS::AccountId}:role/{{ project_name }}-*"
+                - Fn::Sub: "arn:aws:iam::${AWS::AccountId}:role/codeseeder-{{ project_name }}-*"
+                - Fn::Sub: "arn:aws:iam::${AWS::AccountId}:policy/{{ project_name }}-*"
+                - Fn::Sub: "arn:aws:iam::${AWS::AccountId}:policy/codeseeder-{{ project_name }}-*"
+              Sid: DeploymentIAM
+            - Action:
+              - codebuild:Update*
+              - codebuild:Batch*
+              - codebuild:Create*
+              - codebuild:Delete*
+              - codebuild:StartBuild
+              Effect: Allow
+              Resource:
+                Fn::Sub: "arn:aws:codebuild:*:${AWS::AccountId}:project/codeseeder-{{ project_name }}*"
+              Sid: DeploymentCodeBuild
+            - Action:
+              - iam:ListPolicies
+              - ssm:DescribeParameters
+              - codebuild:ListProjects
+              - cloudformation:Describe*
+              - cloudformation:GetTemplate
+              Effect: Allow
+              Resource: '*'
+              Sid: DeploymentListStuff
+            - Action:
+              - cloudformation:*
+              Effect: Allow
+              Resource:
+                - Fn::Sub: "arn:aws:cloudformation:*:${AWS::AccountId}:stack/{{ project_name }}-*"
+                - Fn::Sub: "arn:aws:cloudformation:*:${AWS::AccountId}:stack/aws-codeseeder-{{ project_name }}/*"
+            - Action:
+              - s3:Delete*
+              - s3:Put*
+              - s3:Get*
+              - s3:Restore*
+              - s3:Create*
+              - s3:List*
+              Effect: Allow
+              Resource:
+              - arn:aws:s3:::codeseeder-{{ project_name }}*
+              - arn:aws:s3:::codeseeder-{{ project_name }}*/*
+              Sid: DeploymentS3
+            - Action:
+              - sts:AssumeRole
+              - sts:TagSession
+              - sts:GetSessionToken
+              Effect: Allow
+              Resource:
+              - arn:aws:iam::*:role/{{ project_name }}-*
+              Sid: DeploymentSTS
+            - Action:
+              - ssm:Put*
+              - ssm:Delete*
+              - ssm:Remove*
+              - ssm:Add*
+              - ssm:Describe*
+              - ssm:Get*
+              Effect: Allow
+              Resource:
+                Fn::Sub: "arn:aws:ssm:*:${AWS::AccountId}:parameter/{{ project_name }}/*"
+            - Effect: Allow
+              Action:
+              - logs:CreateLogStream
+              - logs:PutLogEvents
+              - logs:GetLogEvents
+              - logs:GetLogRecord
+              - logs:GetLogGroupFields
+              - logs:GetQueryResults
+              - logs:DescribeLogStreams
+              Resource:
+                Fn::Sub: "arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/codebuild/codeseeder-{{ project_name }}*"
+            - Action:
+              - cloudformation:Create*
+              - cloudformation:Execute*
+              Effect: Allow
+              Resource:
+                Fn::Sub: "arn:aws:cloudformation:*:${AWS::AccountId}:stack/aws-codeseeder-{{ project_name }}*/*"
+            - Action:
+              - codeartifact:*
+              Effect: Allow
+              Resource:
+                - Fn::Sub: "arn:aws:codeartifact:*:${AWS::AccountId}:domain/aws-codeseeder-{{ project_name }}"
+                - Fn::Sub: "arn:aws:codeartifact:*:${AWS::AccountId}:repository/aws-codeseeder-{{ project_name }}*"
+      RoleName: seedfarmer-{{ project_name }}-deployment-role
+    Type: AWS::IAM::Role
diff --git a/seedfarmer/resources/projectpolicy.yaml b/seedfarmer/resources/projectpolicy.yaml
new file mode 100644
index 00000000..7050eb43
--- /dev/null
+++ b/seedfarmer/resources/projectpolicy.yaml
@@ -0,0 +1,79 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: "This stack deploys Project deployment policy"
+Parameters:
+  ProjectName:
+    Type: String
+    Description: The name of the project
+  DeploymentName:
+    Type: String
+    Description: The name of the deployment
+Resources:
+  ProjectPolicy:
+    Type: AWS::IAM::ManagedPolicy
+    Properties:
+      Description: Managed Policy granting access to build a project
+      Path: /
+      PolicyDocument:
+        Statement:
+          - Effect: Allow
+            Action:
+              - "cloudformation:Describe*"
+              - "cloudformation:GetTemplate"
+              - "ec2:Describe*"
+              - "ec2:Get*"
+              - "ecr:Describe*"
+              - "ecr:Get*"
+              - "ecr:List*"
+              - "elasticloadbalancing:Describe*"
+              - "iam:Get*"
+              - "iam:List*"
+              - "logs:CreateLogGroup"
+              - "logs:Describe*"
+              - "logs:DescribeLogGroups"
+              - "s3:List*"
+              - 'kms:CreateKey'
+              - 'kms:Describe*'
+              - 'kms:List*'
+              - 'kms:TagResource'
+            Resource: '*'
+          - Effect: Allow
+            Action:
+              - ssm:Get*
+              - ssm:Describe*
+              - ssm:PutParameter
+              - ssm:DeleteParameter
+              - ssm:DeleteParameters
+            Resource:
+              -  !Sub "arn:aws:ssm::${AWS::AccountId}:parameter/${ProjectName}*"
+          - Effect: Allow
+            Action:
+              - logs:CreateLogStream
+              - logs:PutLogEvents
+              - logs:GetLogEvents
+              - logs:GetLogRecord
+              - logs:GetLogGroupFields
+              - logs:GetQueryResults
+            Resource:
+              - !Sub "arn:aws:logs::${AWS::AccountId}:log-group:/aws/codebuild/codeseeder-${ProjectName}*"
+          - Effect: Allow
+            Action:
+              - ssm:GetParameter
+            Resource:
+              -  !Sub "arn:aws:ssm::${AWS::AccountId}:parameter/cdk-bootstrap*"
+          - Effect: Allow
+            Action:
+              - iam:UpdateAssumeRolePolicy
+            Resource:
+              -  !Sub "arn:aws:iam::${AWS::AccountId}:role/${ProjectName}*"
+          - Effect: Allow
+            Action:
+              - "iam:PassRole"
+              - "sts:AssumeRole"
+            Resource:
+              - !Sub "arn:aws:iam::${AWS::AccountId}:role/cdk*"
+        Version: '2012-10-17'
+
+Outputs:
+  ProjectPolicyARN:
+    Value: !Ref 'ProjectPolicy'
+
diff --git a/seedfarmer/resources/toolchain_role.template b/seedfarmer/resources/toolchain_role.template
new file mode 100644
index 00000000..6477d95a
--- /dev/null
+++ b/seedfarmer/resources/toolchain_role.template
@@ -0,0 +1,47 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: AWS CloudFormation for the SeedFarmer Toolchain Role
+Outputs:
+  ToolchainRoleName:
+    Value:
+      Ref: ToolchainRole
+Resources:
+  ToolchainRole:
+    Properties:
+      AssumeRolePolicyDocument:
+        Version: '2012-10-17'
+        Statement:
+        -   Action: sts:AssumeRole
+            Effect: Allow
+            Principal:
+                AWS: []
+      Path: /
+      Policies:
+        - PolicyName: InlineToolchain
+          PolicyDocument:
+            Statement:
+            - Action:
+              - sts:AssumeRole
+              - sts:TagSession
+              - sts:GetSessionToken
+              Effect: Allow
+              Resource:
+              - arn:aws:iam::*:role/seedfarmer-{{ project_name }}*
+              Sid: ToolChainSTS
+            - Action:
+              - ssm:Put*
+              - ssm:Delete*
+              - ssm:Remove*
+              - ssm:Add*
+              - ssm:Describe*
+              - ssm:Get*
+              Effect: Allow
+              Resource:
+                Fn::Sub: "arn:aws:ssm:*:${AWS::AccountId}:parameter/{{ project_name }}/*"
+              Sid: ToolChainSSM
+            - Action:
+              - ssm:Describe*
+              Effect: Allow
+              Resource:
+                Fn::Sub: "arn:aws:ssm:*:${AWS::AccountId}:*"
+      RoleName: seedfarmer-{{ project_name }}-toolchain-role
+    Type: AWS::IAM::Role
diff --git a/seedfarmer/services/__init__.py b/seedfarmer/services/__init__.py
index aad12d59..91a041a1 100644
--- a/seedfarmer/services/__init__.py
+++ b/seedfarmer/services/__init__.py
@@ -11,3 +11,23 @@
 #    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
+
+from seedfarmer.services._service_utils import (
+    boto3_client,
+    boto3_resource,
+    create_new_session,
+    create_new_session_with_creds,
+    get_account_id,
+    get_botocore_config,
+    get_region,
+)
+
+__all__ = [
+    "get_botocore_config",
+    "get_account_id",
+    "get_region",
+    "boto3_client",
+    "boto3_resource",
+    "create_new_session",
+    "create_new_session_with_creds",
+]
diff --git a/seedfarmer/services/_iam.py b/seedfarmer/services/_iam.py
index 79fdab6e..86a8710c 100644
--- a/seedfarmer/services/_iam.py
+++ b/seedfarmer/services/_iam.py
@@ -16,24 +16,31 @@
 import logging
 from typing import Any, Dict, List, Optional, cast
 
+from boto3 import Session
+
 from seedfarmer.services._service_utils import boto3_client, boto3_resource, get_region
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
 
-def get_role(role_name: str) -> Optional[Dict[str, Any]]:
+def get_role(role_name: str, session: Optional[Session] = None) -> Optional[Dict[str, Any]]:
     _logger.debug("Getting Role: %s", role_name)
 
-    iam_client = boto3_client("iam")
+    iam_client = boto3_client("iam", session=session)
     try:
         return cast(Dict[str, Any], iam_client.get_role(RoleName=role_name))
     except iam_client.exceptions.NoSuchEntityException:
         return None
 
 
-def create_check_iam_role(trust_policy: Dict[str, Any], role_name: str, permission_boundary_arn: Optional[str]) -> None:
+def create_check_iam_role(
+    trust_policy: Dict[str, Any],
+    role_name: str,
+    permissions_boundary_arn: Optional[str],
+    session: Optional[Session] = None,
+) -> None:
     _logger.debug("Creating IAM Role with name: %s ", role_name)
-    iam_client = boto3_client("iam")
+    iam_client = boto3_client("iam", session=session)
     try:
         iam_client.get_role(RoleName=role_name)
     except iam_client.exceptions.NoSuchEntityException:
@@ -41,15 +48,15 @@ def create_check_iam_role(trust_policy: Dict[str, Any], role_name: str, permissi
             "RoleName": role_name,
             "AssumeRolePolicyDocument": json.dumps(trust_policy),
             "Description": f"deployment-role for {role_name}",
-            "Tags": [{"Key": "Region", "Value": get_region()}],
+            "Tags": [{"Key": "Region", "Value": get_region(session=session)}],
         }
-        if permission_boundary_arn:
-            args["PermissionsBoundary"] = permission_boundary_arn
+        if permissions_boundary_arn:
+            args["PermissionsBoundary"] = permissions_boundary_arn
         iam_client.create_role(**args)
 
 
-def attach_policy_to_role(role_name: str, policies: List[str]) -> List[str]:
-    iam_client = boto3_client("iam")
+def attach_policy_to_role(role_name: str, policies: List[str], session: Optional[Session] = None) -> List[str]:
+    iam_client = boto3_client("iam", session=session)
     try:
         response = iam_client.list_attached_role_policies(RoleName=role_name)
         attached_policies = [i["PolicyArn"] for i in response["AttachedPolicies"] if response["AttachedPolicies"]]
@@ -70,8 +77,8 @@ def attach_policy_to_role(role_name: str, policies: List[str]) -> List[str]:
         raise e
 
 
-def attach_inline_policy(role_name: str, policy_body: str, policy_name: str) -> None:
-    iam_client = boto3_client("iam")
+def attach_inline_policy(role_name: str, policy_body: str, policy_name: str, session: Optional[Session] = None) -> None:
+    iam_client = boto3_client("iam", session=session)
     _logger.debug("Attaching the Inline policy %s to the IAM Role: %s", policy_name, role_name)
     try:
         iam_client.put_role_policy(RoleName=role_name, PolicyName=policy_name, PolicyDocument=policy_body)
@@ -80,9 +87,9 @@ def attach_inline_policy(role_name: str, policy_body: str, policy_name: str) ->
         raise e
 
 
-def detach_policy_from_role(role_name: str, policy_arn: str) -> None:
+def detach_policy_from_role(role_name: str, policy_arn: str, session: Optional[Session] = None) -> None:
     _logger.debug("Detatching policy: %s  from the IAM Role: %s ", policy_arn, role_name)
-    iam_client = boto3_client("iam")
+    iam_client = boto3_client("iam", session=session)
     try:
         iam_client.detach_role_policy(RoleName=role_name, PolicyArn=policy_arn)
     except Exception as e:
@@ -90,8 +97,8 @@ def detach_policy_from_role(role_name: str, policy_arn: str) -> None:
         _logger.info("Caught exception: %s ", e)
 
 
-def delete_role(role_name: str) -> None:
-    iam_client = boto3_client("iam")
+def delete_role(role_name: str, session: Optional[Session] = None) -> None:
+    iam_client = boto3_client("iam", session=session)
     try:
         iam_client.delete_role(RoleName=role_name)
     except iam_client.exceptions.NoSuchEntityException as e:
@@ -99,8 +106,8 @@ def delete_role(role_name: str) -> None:
         _logger.info("Caught exception: %s ", e)
 
 
-def detach_inline_policy_from_role(role_name: str, policy_name: str) -> None:
-    iam_resource = boto3_resource("iam")
+def detach_inline_policy_from_role(role_name: str, policy_name: str, session: Optional[Session] = None) -> None:
+    iam_resource = boto3_resource("iam", session=session)
     try:
         iam_resource.RolePolicy(role_name, policy_name).delete()
     except Exception as e:
diff --git a/seedfarmer/services/_secrets_manager.py b/seedfarmer/services/_secrets_manager.py
index 96393d7a..02d73af6 100644
--- a/seedfarmer/services/_secrets_manager.py
+++ b/seedfarmer/services/_secrets_manager.py
@@ -13,13 +13,15 @@
 #    limitations under the License.
 
 import json
-from typing import Any, Dict, cast
+from typing import Any, Dict, Optional, cast
+
+from boto3 import Session
 
 from seedfarmer.services._service_utils import boto3_client, get_account_id, get_region
 
 
-def get_secret_secrets_manager(name: str) -> Dict[str, Any]:
-    client = boto3_client(service_name="secretsmanager")
-    secret_arn = f"arn:aws:secretsmanager:{get_region()}:{get_account_id()}:secret:{name}"
+def get_secret_secrets_manager(name: str, session: Optional[Session] = None) -> Dict[str, Any]:
+    client = boto3_client(service_name="secretsmanager", session=session)
+    secret_arn = f"arn:aws:secretsmanager:{get_region(session=session)}:{get_account_id(session=session)}:secret:{name}"
     json_str: str = client.get_secret_value(SecretId=secret_arn).get("SecretString")
     return cast(Dict[str, Any], json.loads(json_str))
diff --git a/seedfarmer/services/_service_utils.py b/seedfarmer/services/_service_utils.py
index 5c1d7c1b..bcff594a 100644
--- a/seedfarmer/services/_service_utils.py
+++ b/seedfarmer/services/_service_utils.py
@@ -13,22 +13,17 @@
 #    limitations under the License.
 
 import logging
-import math
-from typing import Any, List, Optional
+from typing import Optional
 
 import boto3
 import botocore.exceptions
+from boto3 import Session
 
 import seedfarmer
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
 
-def chunkify(lst: List[Any], num_chunks: int = 1, max_length: Optional[int] = None) -> List[List[Any]]:
-    num: int = num_chunks if max_length is None else int(math.ceil((float(len(lst)) / float(max_length))))
-    return [lst[i : i + num] for i in range(0, len(lst), num)]  # noqa: E203
-
-
 def get_botocore_config() -> botocore.config.Config:
     return botocore.config.Config(
         retries={"max_attempts": 5},
@@ -38,33 +33,79 @@ def get_botocore_config() -> botocore.config.Config:
     )
 
 
-def boto3_client(service_name: str) -> boto3.client:
-    return boto3.Session().client(service_name=service_name, use_ssl=True, config=get_botocore_config())
+def create_new_session(region_name: Optional[str] = None, profile: Optional[str] = None) -> Session:
+    return Session(region_name=region_name, profile_name=profile)
 
 
-def boto3_resource(service_name: str) -> boto3.client:
-    return boto3.Session().resource(service_name=service_name, use_ssl=True, config=get_botocore_config())
+def create_new_session_with_creds(
+    aws_access_key_id: str, aws_secret_access_key: str, aws_session_token: str, region_name: Optional[str] = None
+) -> Session:
+    return boto3.Session(
+        aws_access_key_id=aws_access_key_id,
+        aws_secret_access_key=aws_secret_access_key,
+        aws_session_token=aws_session_token,
+        region_name=region_name,
+    )
 
 
-def get_region() -> str:
-    session = boto3.Session()
-    if session.region_name is None:
+def boto3_client(
+    service_name: str,
+    session: Optional[Session] = None,
+    region_name: Optional[str] = None,
+    profile: Optional[str] = None,
+    aws_access_key_id: Optional[str] = None,
+    aws_secret_access_key: Optional[str] = None,
+    aws_session_token: Optional[str] = None,
+) -> boto3.client:
+    if aws_access_key_id and aws_secret_access_key and aws_session_token:
+        return create_new_session_with_creds(
+            aws_access_key_id, aws_secret_access_key, aws_session_token, region_name
+        ).client(service_name=service_name, use_ssl=True, config=get_botocore_config())
+    elif not session:
+        return create_new_session(region_name, profile).client(
+            service_name=service_name, use_ssl=True, config=get_botocore_config()
+        )
+    else:
+        return session.client(service_name=service_name, use_ssl=True, config=get_botocore_config())
+
+
+def boto3_resource(
+    service_name: str,
+    session: Optional[Session] = None,
+    region_name: Optional[str] = None,
+    profile: Optional[str] = None,
+) -> boto3.client:
+    if not session:
+        return create_new_session(region_name=region_name, profile=profile).resource(
+            service_name=service_name, use_ssl=True, config=get_botocore_config()
+        )
+    else:
+        return session.resource(service_name=service_name, use_ssl=True, config=get_botocore_config())
+
+
+def get_region(session: Optional[Session] = None, profile: Optional[str] = None) -> str:
+    sess = session if session else create_new_session(profile=profile)
+    if sess.region_name is None:
         raise ValueError("It is not possible to infer AWS REGION from your environment.")
-    return str(session.region_name)
+    return str(sess.region_name)
 
 
-def get_account_id() -> str:
+def get_account_id(session: Optional[Session] = None, profile: Optional[str] = None) -> str:
     try:
-        return str(boto3_client(service_name="sts").get_caller_identity().get("Account"))
+        if not session:
+            return str(boto3_client(service_name="sts", profile=profile).get_caller_identity().get("Account"))
+        else:
+            return str(boto3_client(service_name="sts", session=session).get_caller_identity().get("Account"))
     except botocore.exceptions.NoCredentialsError as e:
         _logger.error(f"ERROR: {e}")
         from seedfarmer.output_utils import print_bolded
 
         print_bolded("Please make sure you have valid AWS Credentials", color="red")
-        exit(1)
+        raise e
+
     except botocore.exceptions.ClientError as e:
         _logger.error(f"ERROR: {e}")
         from seedfarmer.output_utils import print_bolded
 
         print_bolded("Please make sure you have a valid AWS Session", color="red")
-        exit(1)
+        raise e
diff --git a/seedfarmer/services/_ssm.py b/seedfarmer/services/_ssm.py
index 85d93d42..f30dda7a 100644
--- a/seedfarmer/services/_ssm.py
+++ b/seedfarmer/services/_ssm.py
@@ -17,13 +17,15 @@
 import time
 from typing import Any, Dict, List, Optional, cast
 
+from boto3 import Session
+
 from seedfarmer.services._service_utils import boto3_client
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
 
-def put_parameter(name: str, obj: Dict[str, Any]) -> None:
-    client = boto3_client(service_name="ssm")
+def put_parameter(name: str, obj: Dict[str, Any], session: Optional[Session] = None) -> None:
+    client = boto3_client(service_name="ssm", session=session)
     retries = 3
     while retries > 0:
         try:
@@ -45,14 +47,14 @@ def put_parameter(name: str, obj: Dict[str, Any]) -> None:
                 raise Exception(err)
 
 
-def get_parameter(name: str) -> Dict[str, Any]:
-    client = boto3_client(service_name="ssm")
+def get_parameter(name: str, session: Optional[Session] = None) -> Dict[str, Any]:
+    client = boto3_client(service_name="ssm", session=session)
     json_str: str = client.get_parameter(Name=name)["Parameter"]["Value"]
     return cast(Dict[str, Any], json.loads(json_str))
 
 
-def get_parameter_if_exists(name: str) -> Optional[Dict[str, Any]]:
-    client = boto3_client(service_name="ssm")
+def get_parameter_if_exists(name: str, session: Optional[Session] = None) -> Optional[Dict[str, Any]]:
+    client = boto3_client(service_name="ssm", session=session)
     try:
         json_str: str = client.get_parameter(Name=name)["Parameter"]["Value"]
     except client.exceptions.ParameterNotFound:
@@ -60,8 +62,8 @@ def get_parameter_if_exists(name: str) -> Optional[Dict[str, Any]]:
     return cast(Dict[str, Any], json.loads(json_str))
 
 
-def does_parameter_exist(name: str) -> bool:
-    client = boto3_client(service_name="ssm")
+def does_parameter_exist(name: str, session: Optional[Session] = None) -> bool:
+    client = boto3_client(service_name="ssm", session=session)
     try:
         client.get_parameter(Name=name)
         return True
@@ -69,8 +71,8 @@ def does_parameter_exist(name: str) -> bool:
         return False
 
 
-def list_parameters(prefix: str) -> List[str]:
-    client = boto3_client(service_name="ssm")
+def list_parameters(prefix: str, session: Optional[Session] = None) -> List[str]:
+    client = boto3_client(service_name="ssm", session=session)
     paginator = client.get_paginator("describe_parameters")
     response_iterator = paginator.paginate(
         ParameterFilters=[
@@ -91,8 +93,8 @@ def list_parameters(prefix: str) -> List[str]:
     return ret
 
 
-def list_parameters_with_filter(prefix: str, contains_string: str) -> List[str]:
-    client = boto3_client(service_name="ssm")
+def list_parameters_with_filter(prefix: str, contains_string: str, session: Optional[Session] = None) -> List[str]:
+    client = boto3_client(service_name="ssm", session=session)
     paginator = client.get_paginator("describe_parameters")
 
     response_iterator = paginator.paginate(
@@ -115,8 +117,8 @@ def list_parameters_with_filter(prefix: str, contains_string: str) -> List[str]:
     return ret
 
 
-def get_all_parameter_data_by_path(prefix: str) -> Dict[str, Any]:
-    client = boto3_client(service_name="ssm")
+def get_all_parameter_data_by_path(prefix: str, session: Optional[Session] = None) -> Dict[str, Any]:
+    client = boto3_client(service_name="ssm", session=session)
     paginator = client.get_paginator("get_parameters_by_path")
     response_iterator = paginator.paginate(
         Path=prefix,
@@ -138,11 +140,12 @@ def get_all_parameter_data_by_path(prefix: str) -> Dict[str, Any]:
     return ret
 
 
-def delete_parameters(parameters: List[str]) -> None:
+def delete_parameters(parameters: List[str], session: Optional[Session] = None) -> None:
     if parameters:
         if len(parameters) < 10:
-            client = boto3_client(service_name="ssm")
+            _logger.debug("deleting parameters: %s", parameters)
+            client = boto3_client(service_name="ssm", session=session)
             client.delete_parameters(Names=parameters)
         else:
-            delete_parameters(parameters[0:9])
-            delete_parameters(parameters[9 : len(parameters)])
+            delete_parameters(parameters[0:9], session=session)
+            delete_parameters(parameters[9:], session=session)
diff --git a/seedfarmer/services/session_manager.py b/seedfarmer/services/session_manager.py
new file mode 100644
index 00000000..3d73157c
--- /dev/null
+++ b/seedfarmer/services/session_manager.py
@@ -0,0 +1,178 @@
+import logging
+import threading
+from abc import abstractmethod, abstractproperty
+from threading import Thread
+from time import sleep
+from typing import Any, Dict, List, Optional, Tuple
+
+from boto3 import Session
+
+from seedfarmer.services import boto3_client, create_new_session, create_new_session_with_creds, get_account_id
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+class SingletonMeta(type):
+    """
+    This is a thread-safe implementation of Singleton.
+    """
+
+    _instances: Dict[Any, Any] = {}
+
+    _lock: threading.Lock = threading.Lock()
+
+    def __call__(cls, *args: List[Any], **kwargs: Dict[Any, Any]) -> Any:
+        """
+        Possible changes to the value of the `__init__` argument do not affect
+        the returned instance.
+        """
+        with cls._lock:
+            if cls not in cls._instances:
+                instance = super().__call__(*args, **kwargs)
+                cls._instances[cls] = instance
+        return cls._instances[cls]
+
+
+class ISessionManager(object):
+    @abstractmethod
+    def get_or_create(
+        self,
+        *,
+        project_name: Optional[str] = None,
+        region_name: Optional[str] = None,
+        profile: Optional[str] = None,
+        enable_reaper: bool = False,
+        **kwargs: Optional[Any],
+    ) -> Any:
+        ...
+
+    @abstractproperty
+    def toolchain_session(self) -> Session:
+        ...
+
+    @abstractmethod
+    def get_deployment_session(self, account_id: str, region_name: str) -> Session:
+        ...
+
+
+class SessionManager(ISessionManager, metaclass=SingletonMeta):
+    TOOLCHAIN_KEY: str = "toolchain"
+    SESSION: str = "session"
+    ROLE: str = "role"
+    sessions: Dict[Any, Any] = {}
+
+    config: Dict[Any, Any] = {}
+    created: bool = False
+    reaper: Thread = None  # type: ignore
+    reaperInterval: int = 3000  # every 50 minutes
+
+    def __init__(self) -> None:
+        super().__init__()
+
+    def get_or_create(
+        self,
+        *,
+        project_name: Optional[str] = None,
+        region_name: Optional[str] = None,
+        profile: Optional[str] = None,
+        enable_reaper: bool = False,
+        **kwargs: Optional[Any],
+    ) -> ISessionManager:
+        if not self.created:
+            if project_name is None:
+                raise ValueError("A 'project_name' is required for first time initialization of the SessionManager")
+            self.config["project_name"] = project_name
+            self.config["region_name"] = region_name
+            self.config["profile"] = profile
+            self.config = {**self.config, **kwargs}
+            self.toolchain_role_name = f"seedfarmer-{project_name}-toolchain-role"
+
+            if enable_reaper and (not self.reaper or not self.reaper.is_alive()):
+                self._setup_reaper()
+
+            self._check_for_toolchain()
+            self.created = True
+        return self
+
+    @property
+    def toolchain_session(self) -> Session:
+        if not self.created:
+            raise RuntimeError("The SessionManager object was never properly created...)")
+        self._check_for_toolchain()
+        return self.sessions[self.TOOLCHAIN_KEY][self.SESSION]
+
+    def get_deployment_session(self, account_id: str, region_name: str) -> Session:
+        session_key = f"{account_id}-{region_name}"
+        project_name = self.config["project_name"]
+        if not self.created:
+            raise RuntimeError("The SessionManager object was never properly created...")
+        if session_key not in self.sessions.keys():
+            _logger.info(f"Creating Session for {session_key}")
+            self._check_for_toolchain()
+            toolchain_role = self.sessions[self.TOOLCHAIN_KEY][self.ROLE]
+            deployment_role_arn = f"arn:aws:iam::{account_id}:role/seedfarmer-{project_name}-deployment-role"
+            # the boto sessions are not thread safe, so create a new one for the toolchain role every time to be sure
+            sts_toolchain_client = boto3_client(
+                service_name="sts",
+                aws_access_key_id=toolchain_role["Credentials"]["AccessKeyId"],
+                aws_secret_access_key=toolchain_role["Credentials"]["SecretAccessKey"],
+                aws_session_token=toolchain_role["Credentials"]["SessionToken"],
+            )
+            deployment_role = sts_toolchain_client.assume_role(
+                RoleArn=deployment_role_arn,
+                RoleSessionName="deployment_role",
+            )
+            deployment_session = create_new_session_with_creds(
+                aws_access_key_id=deployment_role["Credentials"]["AccessKeyId"],
+                aws_secret_access_key=deployment_role["Credentials"]["SecretAccessKey"],
+                aws_session_token=deployment_role["Credentials"]["SessionToken"],
+                region_name=region_name,
+            )
+            self.sessions[session_key] = {self.SESSION: deployment_session, self.ROLE: deployment_role}
+            return deployment_session
+        else:
+            _logger.info(f"Session Found for {session_key}")
+            return self.sessions[session_key][self.SESSION]
+
+    # These methods below should not be called outside of this class
+
+    def _check_for_toolchain(self) -> None:
+        if self.TOOLCHAIN_KEY not in self.sessions.keys():
+            _logger.info("Creating toolchain session")
+            session, role = self._get_toolchain()
+            self.sessions = {self.TOOLCHAIN_KEY: {self.SESSION: session, self.ROLE: role}}
+
+    def _get_toolchain(self) -> Tuple[Session, Dict[Any, Any]]:
+        region_name = self.config.get("region_name")
+        profile_name = self.config.get("profile")
+        _logger.debug("Getting toolchain role")
+        user_session = create_new_session(region_name=region_name, profile=profile_name)
+        user_client = boto3_client(service_name="sts", session=user_session)
+        toolchain_account_id = get_account_id(user_session)
+
+        toolchain_role_arn = f"arn:aws:iam::{toolchain_account_id}:role/{self.toolchain_role_name}"
+        toolchain_role = user_client.assume_role(
+            RoleArn=toolchain_role_arn,
+            RoleSessionName="toolchainrole",
+        )
+        toolchain_session = create_new_session_with_creds(
+            aws_access_key_id=toolchain_role["Credentials"]["AccessKeyId"],
+            aws_secret_access_key=toolchain_role["Credentials"]["SecretAccessKey"],
+            aws_session_token=toolchain_role["Credentials"]["SessionToken"],
+            region_name=region_name,
+        )
+
+        return toolchain_session, toolchain_role
+
+    def _setup_reaper(self) -> None:
+        _logger.info("Starting Session Reaper")
+        t = Thread(target=self._reap_sessions, args=(self.reaperInterval,), daemon=True, name="SessionReaper")
+        t.start()
+        self.reaper = t
+
+    def _reap_sessions(self, interval: int) -> None:
+        _logger.debug("Reaper Is Set")
+        while True:
+            sleep(interval)
+            _logger.info(f"Reaping Sessions - sleeping for {interval} seconds")
+            self.sessions = {}
diff --git a/seedfarmer/utils.py b/seedfarmer/utils.py
index 2c2ae60c..6b65e7a4 100644
--- a/seedfarmer/utils.py
+++ b/seedfarmer/utils.py
@@ -14,9 +14,11 @@
 
 import hashlib
 import logging
+from typing import Optional
 
 import humps
 import yaml
+from boto3 import Session
 
 from seedfarmer.services._service_utils import get_account_id, get_region
 
@@ -33,7 +35,7 @@ class CfnSafeYamlLoader(yaml.SafeLoader):
     Parameters
     ----------
     yaml : _type_
-    """ """"""
+    """
 
     yaml_implicit_resolvers = {
         k: [r for r in v if r[0] != "tag:yaml.org,2002:timestamp"]
@@ -54,7 +56,7 @@ def upper_snake_case(value: str) -> str:
     -------
     str
         the string standardized
-    """ """"""
+    """
     if humps.is_camelcase(value):  # type: ignore
         return humps.decamelize(value).upper()  # type: ignore
     elif humps.is_pascalcase(value):  # type: ignore
@@ -63,7 +65,7 @@ def upper_snake_case(value: str) -> str:
         return value.replace("-", "_").upper()
 
 
-def generate_hash() -> str:
+def generate_hash(session: Optional[Session] = None) -> str:
     """
     Generate a hexdigest hash of the project and the deployment - for use generating unique names
 
@@ -72,8 +74,8 @@ def generate_hash() -> str:
     str
         The resulting hash as a string
     """
-    account = get_account_id()
-    region = get_region()
+    account = get_account_id(session=session)
+    region = get_region(session=session)
     concatenated_string = f"{account}-{region}"
     hash_value = (hashlib.sha1(concatenated_string.encode("UTF-8")).hexdigest())[:8]
     _logger.debug("HASH generated is %s", hash_value)
diff --git a/setup.cfg b/setup.cfg
index 78a4ad25..a26bd01c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -24,4 +24,4 @@ strict = True
 ignore_missing_imports = True
 allow_untyped_decorators = True
 exclude =
-  codeseeder.out/|example/
\ No newline at end of file
+  codeseeder.out/|examples/|modules/|test/
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 6fcd5b1f..34068756 100644
--- a/setup.py
+++ b/setup.py
@@ -45,7 +45,7 @@
     keywords=["aws", "cdk"],
     python_requires=">=3.7",
     install_requires=[
-        "aws-codeseeder~=0.4.0",
+        "aws-codeseeder~=0.5.2",
         "cookiecutter~=2.1.0",
         "pyhumps~=3.5.0",
         "pydantic~=1.9.0",
@@ -54,6 +54,7 @@
         "checksumdir~=1.2.0",
         "rich~=12.4.0",
         "requests==2.28.1",
+        "python-dotenv~=0.21.0",
     ],
     entry_points={"console_scripts": ["seedfarmer = seedfarmer.__main__:main"]},
     classifiers=[


Test patch:
diff --git a/test/pytest.ini b/test/pytest.ini
index 58380634..f082c45a 100644
--- a/test/pytest.ini
+++ b/test/pytest.ini
@@ -1,5 +1,5 @@
 [pytest]
-markers = 
+markers =
     first: marks the first test to run # from pytest-ordering
     second: marks the seconds test to run
     last: marks the last test to run
@@ -15,4 +15,7 @@ markers =
     store: marks all `seedfarmer store` tests
     store_md5: marks all `seedfarmer store` tests
     store_moduledata: marks all `seedfarmer store` tests
+    models: marks all `models` tests
+    models_deployment_manifest: marks all `DeploymentManifest` tests
+    models_module_manifest: marks all `ModuleManifest` tests
 log_cli_level = INFO
\ No newline at end of file
diff --git a/test/pytest.sh b/test/pytest.sh
index 15895832..96b35293 100755
--- a/test/pytest.sh
+++ b/test/pytest.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 
-pytest test/unit-test/test_cli_arg.py -v --cov=seedfarmer --cov-report=html --cov-report=xml
+pytest test/unit-test/*.py -v --cov=seedfarmer --cov-report=html --cov-report=xml
diff --git a/test/unit-test/mock_data/manifests/module-test/deployment.yaml b/test/unit-test/mock_data/manifests/module-test/deployment.yaml
index ed74034a..8dde41c3 100644
--- a/test/unit-test/mock_data/manifests/module-test/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/module-test/deployment.yaml
@@ -1,4 +1,17 @@
 name: example-test-dev
+toolchainRegion: us-west-2
 groups:
   - name: test
-    path: test/unit-test/mock_data/manifests/module-test/test-modules.yaml
+    path: manifests/module-test/test-modules.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/mock_data/manifests/module-test/test-modules.yaml b/test/unit-test/mock_data/manifests/module-test/test-modules.yaml
index f8bfd2f1..f7b66a6d 100644
--- a/test/unit-test/mock_data/manifests/module-test/test-modules.yaml
+++ b/test/unit-test/mock_data/manifests/module-test/test-modules.yaml
@@ -1,5 +1,5 @@
 name: test-module
-path: test/unit-test/mock_data/modules/module-test/
+path: modules/module-test/
 parameters:
   - name: domain_name_prefix
-    value: testdomaindomaintest
+    value: testdomaindomaintester
diff --git a/test/unit-test/mock_data/manifests/test-broken-deployspec-deploy/deployment.yaml b/test/unit-test/mock_data/manifests/test-broken-deployspec-deploy/deployment.yaml
index cfbbf3da..baf6f2f0 100644
--- a/test/unit-test/mock_data/manifests/test-broken-deployspec-deploy/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/test-broken-deployspec-deploy/deployment.yaml
@@ -1,4 +1,17 @@
 name: test-broken-deployspec-deploy
+toolchainRegion: us-west-2
 groups:
   - name: test
-    path: test/unit-test/mock_data/manifests/test-broken-deployspec-deploy/test-module.yaml
+    path: manifests/test-broken-deployspec-deploy/test-module.yamlxddd
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/deployment.yaml b/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/deployment.yaml
index 323f7494..542e7193 100644
--- a/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/deployment.yaml
@@ -1,4 +1,17 @@
 name: test-broken-deployspec-destroy
+toolchainRegion: us-west-2
 groups:
   - name: test
-    path: test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/test-module.yaml
+    path: manifests/test-broken-deployspec-destroy/test-module.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/test-module.yaml b/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/test-module.yaml
index bf4bd125..dd4a5e0b 100644
--- a/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/test-module.yaml
+++ b/test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/test-module.yaml
@@ -1,2 +1,2 @@
 name: test-bucket
-path: test/unit-test/mock_data/modules/module-test-broken-deploy/
+path: modules/module-test-broken-deploy/
diff --git a/test/unit-test/mock_data/manifests/test-missing-deployment-group-name/deployment.yaml b/test/unit-test/mock_data/manifests/test-missing-deployment-group-name/deployment.yaml
index d9fbe467..92c64ae4 100644
--- a/test/unit-test/mock_data/manifests/test-missing-deployment-group-name/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/test-missing-deployment-group-name/deployment.yaml
@@ -1,4 +1,17 @@
 name: test-missing-deployment-group-name
+toolchainRegion: us-west-2
 groups:
-  - name: 
-    path: test/unit-test/mock_data/manifests/test-missing-deployment-group-name/test-module.yaml
+  - name:
+    path: manifests/test-missing-deployment-group-name/test-module.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/mock_data/manifests/test-missing-deployment-group-path/deployment.yaml b/test/unit-test/mock_data/manifests/test-missing-deployment-group-path/deployment.yaml
index aa0771c0..b79372c7 100644
--- a/test/unit-test/mock_data/manifests/test-missing-deployment-group-path/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/test-missing-deployment-group-path/deployment.yaml
@@ -1,4 +1,17 @@
 name: test-missing-deployment-group-path
+toolchainRegion: us-west-2
 groups:
   - name: test-missing-path
-    path: 
+    path:
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/mock_data/manifests/test-missing-deployment-name/deployment.yaml b/test/unit-test/mock_data/manifests/test-missing-deployment-name/deployment.yaml
index 5481ae4d..91bf4271 100644
--- a/test/unit-test/mock_data/manifests/test-missing-deployment-name/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/test-missing-deployment-name/deployment.yaml
@@ -1,4 +1,17 @@
 name:
+toolchainRegion: us-west-2
 groups:
   - name: test
     path: test/unit-test/mock_data/manifests/test-missing-deployment-name/test-module.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/mock_data/manifests/test-missing-group-manifest/deployment.yaml b/test/unit-test/mock_data/manifests/test-missing-group-manifest/deployment.yaml
index a51ae6b7..1504165c 100644
--- a/test/unit-test/mock_data/manifests/test-missing-group-manifest/deployment.yaml
+++ b/test/unit-test/mock_data/manifests/test-missing-group-manifest/deployment.yaml
@@ -1,4 +1,17 @@
 name: test-missing-group-manifest
+toolchainRegion: us-west-2
 groups:
   - name: test
     path: test/unit-test/mock_data/manifests/test-missing-group-manifest/test-module.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    # parametersGlobal:
+    #   permissionsBoundaryName: policy-name
+    #   dockerCredentialsSecret: some-secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
diff --git a/test/unit-test/test_cli_arg.py b/test/unit-test/test_cli_arg.py
index 0a3c39c0..26f999b4 100644
--- a/test/unit-test/test_cli_arg.py
+++ b/test/unit-test/test_cli_arg.py
@@ -12,21 +12,19 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
-import json
 import logging
 import os
 
 import pytest
 from _test_helper_functions import _test_command
 
-import seedfarmer.commands._stack_commands as _sc
-import seedfarmer.mgmt.module_init as minit
-from seedfarmer.__main__ import PROJECT, apply, destroy, init
+from seedfarmer import config
+from seedfarmer.__main__ import apply, destroy, init
 from seedfarmer.__main__ import list as _list
 from seedfarmer.__main__ import remove, store
 
-# Override _stack_commands OPS_ROOT to reflect path of resource policy needed for some testing #
-_sc.OPS_ROOT = os.path.join(_sc.OPS_ROOT, "test/unit-test/mock_data")
+# Override OPS_ROOT to reflect path of resource policy needed for some testing #
+config.OPS_ROOT = os.path.join(config.OPS_ROOT, "test/unit-test/mock_data")
 
 _logger: logging.Logger = logging.getLogger(__name__)
 
@@ -38,7 +36,7 @@
 @pytest.mark.init
 def test_init_create_module():
     module_name = "test-module"
-    expected_module_path = os.path.join(minit.OPS_ROOT, "modules")
+    expected_module_path = os.path.join(config.OPS_ROOT, "modules")
 
     # Creates a new module
     _test_command(sub_command=init, options=["module", "-m", module_name], exit_code=0, return_result=False)
@@ -56,7 +54,7 @@ def test_init_create_module():
 def test_init_create_group_module():
     module_name = "test-module"
     group_name = "group"
-    expected_module_path = os.path.join(minit.OPS_ROOT, "modules", group_name, module_name)
+    expected_module_path = os.path.join(config.OPS_ROOT, "modules", group_name, module_name)
 
     # Creates a group and a module within the group
     _test_command(sub_command=init, options=["module", "-g", group_name, "-m", module_name], exit_code=0)
@@ -68,7 +66,7 @@ def test_init_create_group_module():
     )
     assert (
         result.exception.args[0]
-        == f"The module {module_name} already exists under {minit.OPS_ROOT}/modules/{group_name}."
+        == f"The module {module_name} already exists under {config.OPS_ROOT}/modules/{group_name}."
     )
 
     # Checks if a file from the project template was created within the new module
@@ -76,15 +74,17 @@ def test_init_create_group_module():
 
 
 @pytest.mark.init
-def test_init_create_project(tmp_path):
-    expected_project_path = os.path.join(minit.OPS_ROOT, PROJECT)
+def test_init_create_project():
+    expected_project_path = os.path.join(config.OPS_ROOT, config.PROJECT)
 
     # Creates a new project
     _test_command(sub_command=init, options=["project"], exit_code=0, return_result=False)
 
     # Creates a project that already exist
     result = _test_command(sub_command=init, options=["project"], exit_code=1, return_result=True)
-    assert result.exception.args[0] == f'Error: "{os.path.join(minit.OPS_ROOT, PROJECT)}" directory already exists'
+    assert (
+        result.exception.args[0] == f'Error: "{os.path.join(config.OPS_ROOT, config.PROJECT)}" directory already exists'
+    )
 
     # Checks if file exists from the project template
     assert os.path.exists(os.path.join(expected_project_path, "seedfarmer.yaml"))
@@ -101,7 +101,7 @@ def test_apply_help():
         sub_command=apply,
         options=["--help"],
         exit_code=0,
-        expected_output=f"Apply a deployment manifest relative path for {PROJECT.upper()}",
+        expected_output="Apply manifests to a SeedFarmer managed deployment",
     )
 
 
@@ -109,14 +109,14 @@ def test_apply_help():
 @pytest.mark.apply_working_module
 def test_apply_deployment():
     # Deploys a functioning module
-    deployment_manifest = "test/unit-test/mock_data/manifests/module-test/deployment.yaml"
+    deployment_manifest = "manifests/module-test/deployment.yaml"
 
     _test_command(sub_command=apply, options=deployment_manifest, exit_code=0)
 
 
 @pytest.mark.apply
 def test_apply_missing_deployment():
-    deployment_manifest = "test/unit-test/mock_data/manifests/test-missing-deployment-manifest/deployment.yaml"
+    deployment_manifest = "manifests/test-missing-deployment-manifest/deployment.yaml"
 
     result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
     assert result.exception.args[1] == "No such file or directory"
@@ -124,7 +124,7 @@ def test_apply_missing_deployment():
 
 @pytest.mark.apply
 def test_apply_missing_group_manifest():
-    deployment_manifest = "test/unit-test/mock_data/manifests/test-missing-group-manifest/deployment.yaml"
+    deployment_manifest = "manifests/test-missing-group-manifest/deployment.yaml"
 
     result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
     assert result.exception.args[1] == "No such file or directory"
@@ -132,7 +132,7 @@ def test_apply_missing_group_manifest():
 
 @pytest.mark.apply
 def test_apply_missing_deployment_group_name():
-    deployment_manifest = "test/unit-test/mock_data/manifests/test-missing-deployment-group-name/deployment.yaml"
+    deployment_manifest = "manifests/test-missing-deployment-group-name/deployment.yaml"
 
     result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
     assert result.exception.args[0][0][0].exc.errors()[0]["msg"] == "none is not an allowed value"
@@ -140,7 +140,7 @@ def test_apply_missing_deployment_group_name():
 
 @pytest.mark.apply
 def test_apply_missing_deployment_group_path():
-    deployment_manifest = "test/unit-test/mock_data/manifests/test-missing-deployment-group-path/deployment.yaml"
+    deployment_manifest = "manifests/test-missing-deployment-group-path/deployment.yaml"
 
     result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
     assert result.exception.args[0] == "One of the `path` or `modules` attributes must be defined on a Group"
@@ -148,7 +148,7 @@ def test_apply_missing_deployment_group_path():
 
 @pytest.mark.apply
 def test_apply_missing_deployment_name():
-    deployment_manifest = "test/unit-test/mock_data/manifests/test-missing-deployment-name/deployment.yaml"
+    deployment_manifest = "manifests/test-missing-deployment-name/deployment.yaml"
 
     result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
     assert result.exception.args[0][0].exc.msg_template == "none is not an allowed value"
@@ -156,16 +156,16 @@ def test_apply_missing_deployment_name():
 
 @pytest.mark.apply
 def test_apply_broken_deploy_phase():
-    deployment_manifest = "test/unit-test/mock_data/manifests/test-broken-deployspec-deploy/deployment.yaml"
+    deployment_manifest = "manifests/test-broken-deployspec-deploy/deployment.yaml"
 
-    result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
-    assert result.exception.args[0][0].exc.errors()[0]["msg"] == "none is not an allowed value"
+    _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
+    # assert result.exception.args[0][0].exc.errors()[0]["msg"] == "none is not an allowed value"
 
 
 # # TODO add test for broken destroy phase
 # @pytest.mark.destroy
 # def test_destroy_broken_deploy_phase():
-#     deployment_manifest = "test/unit-test/mock_data/manifests/test-broken-deployspec-destroy/deployment.yaml"
+#     deployment_manifest = "manifests/test-broken-deployspec-destroy/deployment.yaml"
 
 #     result = _test_command(sub_command=destroy, options=deployment_manifest, exit_code=1, return_result=True)
 #     assert result.exception.args[0][0].exc.errors()[0]['msg'] == "none is not an allowed value"
@@ -298,16 +298,17 @@ def test_list_moduledata_non_existent_module():
     )
 
 
-@pytest.mark.list
-@pytest.mark.list_moduledata
-def test_list_moduledata():
-    result = _test_command(
-        sub_command=_list,
-        options=["moduledata", "-d", "example-test-dev", "-g", "test", "-m", "test-module"],
-        exit_code=0,
-        return_result=True,
-    )
-    assert json.loads(result.output).get("CognitoDomainName") == "testdomaindomaintest"
+# temp disabled
+# @pytest.mark.list
+# @pytest.mark.list_moduledata
+# def test_list_moduledata():
+#     result = _test_command(
+#         sub_command=_list,
+#         options=["moduledata", "-d", "example-test-dev", "-g", "test", "-m", "test-module"],
+#         exit_code=0,
+#         return_result=True,
+#     )
+#     assert json.loads(result.output).get("CognitoDomainName") == "testdomaindomaintester"
 
 
 # Test `list modules` #
@@ -320,7 +321,7 @@ def test_list_modules_help():
         sub_command=_list,
         options=["modules", "--help"],
         exit_code=0,
-        expected_output="List the modules in a group",
+        expected_output="List the modules in a deployment",
     )
 
 
@@ -587,15 +588,16 @@ def test_store_moduledata_missing_module_arg():
 @pytest.mark.destroy
 def test_destroy():
     _test_command(
-        sub_command=destroy, options=["--help"], exit_code=0, expected_output=f"Destroy {PROJECT.upper()} Deployment"
+        sub_command=destroy, options=["--help"], exit_code=0, expected_output="Destroy a SeedFarmer managed deployment"
     )
 
 
-@pytest.mark.last
-@pytest.mark.destroy
-def test_destroy_working_module():
-    # Destroys test_apply_deployment()
-    _test_command(sub_command=destroy, options="example-test-dev", exit_code=0, return_result=False)
+# temp disabled
+# @pytest.mark.last
+# @pytest.mark.destroy
+# def test_destroy_working_module():
+#     # Destroys test_apply_deployment()
+#     _test_command(sub_command=destroy, options="example-test-dev", exit_code=0, return_result=False)
 
 
 @pytest.mark.destroy
diff --git a/test/unit-test/test_models.py b/test/unit-test/test_models.py
new file mode 100644
index 00000000..143f1885
--- /dev/null
+++ b/test/unit-test/test_models.py
@@ -0,0 +1,225 @@
+#  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License").
+#    You may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+import logging
+from copy import deepcopy
+
+import pytest
+import yaml
+
+from seedfarmer.models.manifests import DeploymentManifest, ModuleManifest
+
+# Override _stack_commands OPS_ROOT to reflect path of resource policy needed for some testing #
+# _sc.OPS_ROOT = os.path.join(_sc.OPS_ROOT, "test/unit-test/mock_data")
+
+_logger: logging.Logger = logging.getLogger(__name__)
+
+
+def pytest_configure(config):
+    config.addinivalue_line("markers", "models")
+    config.addinivalue_line("markers", "models_deployment_manifest")
+
+
+deployment_yaml = yaml.safe_load(
+    """
+name: test
+toolchainRegion: us-west-2
+groups:
+  - name: optionals
+    path: manifests/test/optional-modules.yaml
+targetAccountMappings:
+  - alias: primary
+    accountId: "000000000000"
+    default: true
+    parametersGlobal:
+      permissionsBoundaryName: policyName
+      dockerCredentialsSecret: secret
+    regionMappings:
+      - region: us-west-2
+        default: true
+        parametersRegional:
+          someKey: someValue
+"""
+)
+
+
+@pytest.mark.models
+@pytest.mark.models_deployment_manifest
+def test_deserialize_deployment_manifest():
+    manifest = DeploymentManifest(**deployment_yaml)
+    assert manifest.name == "test"
+
+
+@pytest.mark.models
+@pytest.mark.models_deployment_manifest
+def test_deployment_manifest_get_parameter_with_defaults():
+    manifest = DeploymentManifest(**deployment_yaml)
+
+    assert manifest.get_parameter_value("someKey", account_alias="primary", region="us-west-2") == "someValue"
+    assert manifest.get_parameter_value("someKey", account_id="000000000000", region="us-west-2") == "someValue"
+    assert (
+        manifest.get_parameter_value("dockerCredentialsSecret", account_alias="primary", region="us-west-2") == "secret"
+    )
+    assert manifest.get_parameter_value("dockerCredentialsSecret") == "secret"
+    assert manifest.get_parameter_value("noKey") is None
+    assert manifest.get_parameter_value("noKey", default="noValue") == "noValue"
+    with pytest.raises(ValueError):
+        manifest.get_parameter_value("someKey", account_alias="primary", account_id="000000000000", region="us-west-2")
+
+    assert manifest.get_target_account_mapping(account_alias="primary").alias == "primary"
+    assert manifest.get_target_account_mapping(account_id="000000000000").alias == "primary"
+    assert manifest.get_target_account_mapping(account_alias="other") is None
+    assert manifest.get_target_account_mapping(account_id="other") is None
+    assert manifest.default_target_account_mapping.alias == "primary"
+
+    assert manifest.target_account_mappings[0].get_region_mapping(region="us-west-2").region == "us-west-2"
+    assert manifest.target_account_mappings[0].get_region_mapping(region="other") is None
+    assert manifest.target_account_mappings[0].default_region_mapping.region == "us-west-2"
+
+    with pytest.raises(ValueError):
+        manifest.get_target_account_mapping(account_alias="primary", account_id="000000000000")
+    with pytest.raises(ValueError):
+        manifest.get_target_account_mapping()
+
+
+@pytest.mark.models
+@pytest.mark.models_deployment_manifest
+def test_deployment_manifest_get_parameter_without_defaults():
+    # Clear targetAccountMapping and regionMapping defaults
+    updated_deployment_yaml = deepcopy(deployment_yaml)
+    updated_deployment_yaml["targetAccountMappings"][0]["default"] = False
+    updated_deployment_yaml["targetAccountMappings"][0]["regionMappings"][0]["default"] = False
+    manifest = DeploymentManifest(**updated_deployment_yaml)
+
+    assert manifest.get_parameter_value("dockerCredentialsSecret") is None
+    assert manifest.default_target_account_mapping is None
+    assert manifest.target_account_mappings[0].default_region_mapping is None
+
+
+@pytest.mark.models
+@pytest.mark.models_module_manifest
+def test_module_manifest_with_defaults():
+    manifest = DeploymentManifest(**deployment_yaml)
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+targetAccount: primary
+targetRegion: us-west-2
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+    manifest.validate_and_set_module_defaults()
+
+    assert module.target_account == "primary"
+    assert module.target_region == "us-west-2"
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+    manifest.validate_and_set_module_defaults()
+
+    assert module.target_account == "primary"
+    assert module.target_region == "us-west-2"
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+targetAccount: other
+targetRegion: us-west-2
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+    with pytest.raises(ValueError):
+        manifest.validate_and_set_module_defaults()
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+targetAccount: primary
+targetRegion: other
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+    with pytest.raises(ValueError):
+        manifest.validate_and_set_module_defaults()
+
+
+@pytest.mark.models
+@pytest.mark.models_module_manifest
+def test_module_manifest_without_defaults():
+    # Clear targetAccountMapping and regionMapping defaults
+    updated_deployment_yaml = deepcopy(deployment_yaml)
+    updated_deployment_yaml["targetAccountMappings"][0]["default"] = False
+    updated_deployment_yaml["targetAccountMappings"][0]["regionMappings"][0]["default"] = False
+    manifest = DeploymentManifest(**updated_deployment_yaml)
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+targetAccount: primary
+targetRegion: us-west-2
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+    manifest.validate_and_set_module_defaults()
+
+    assert module.target_account == "primary"
+    assert module.target_region == "us-west-2"
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+targetAccount: primary
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+
+    with pytest.raises(ValueError):
+        manifest.validate_and_set_module_defaults()
+
+    module_yaml = yaml.safe_load(
+        """
+name: test-module-1
+path: modules/test-module
+targetRegion: us-west-2
+"""
+    )
+
+    module = ModuleManifest(**module_yaml)
+    manifest.groups[0].modules = [module]
+
+    with pytest.raises(ValueError):
+        manifest.validate_and_set_module_defaults()