diff --git a/README.md b/README.md
index 9f38ea6a..bb586d52 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ raptor import project-type --managed facets/aws --name "My Platform"
`Cloud Account (aws_provider)` `Network/VPC (aws_network)` `EKS Standard (eks_standard)` `EKS Automode (eks_automode)` `Node Pool/Karpenter (karpenter)` `Node Pool/Automode (eks_automode)` `Karpenter (default)` `AWS ALB Controller (standard)` `EFS CSI Driver (standard)` `AWS EFS (standard)` `StorageClass/EBS (aws_ebs)` `StorageClass/EFS (aws_efs)` `Service (aws)` `AWS IAM Policy (standard)` `AWS IAM Role (standard)`
**Managed Datastores**
-`PostgreSQL/RDS (aws-rds)` `PostgreSQL/Aurora (aws-aurora)` `MySQL/RDS (aws-rds)` `MySQL/Aurora (aws-aurora)` `MongoDB/DocumentDB (aws-documentdb)` `Redis/ElastiCache (aws-elasticache)` `Kafka/MSK (aws-msk)`
+`PostgreSQL/RDS (aws-rds)` `PostgreSQL/Aurora (aws-aurora)` `PostgreSQL/Logical (logical)` `MySQL/RDS (aws-rds)` `MySQL/Aurora (aws-aurora)` `MongoDB/DocumentDB (aws-documentdb)` `Redis/ElastiCache (aws-elasticache)` `Kafka/MSK (aws-msk)`
**Self-hosted via KubeBlocks**
`PostgreSQL` `MySQL` `MongoDB` `Redis`
@@ -91,7 +91,7 @@ raptor import project-type --managed facets/gcp --name "My Platform"
`Cloud Account (gcp_provider)` `Network/VPC (gcp_network)` `GKE (gke)` `Node Pool (gcp)` `Node Fleet (gcp_node_fleet)` `Service (gcp)` `Workload Identity (gcp)` `Pub/Sub (gcp)` `GCP Secret Manager (gcp)`
**Managed Datastores**
-`PostgreSQL/Cloud SQL (gcp-cloudsql)` `MySQL/Cloud SQL (gcp-cloudsql)` `Redis/Memorystore (gcp-memorystore)`
+`PostgreSQL/Cloud SQL (gcp-cloudsql)` `PostgreSQL/Logical (logical)` `MySQL/Cloud SQL (gcp-cloudsql)` `Redis/Memorystore (gcp-memorystore)`
**Self-hosted via KubeBlocks**
`PostgreSQL` `MySQL` `MongoDB` `Redis`
@@ -137,7 +137,7 @@ raptor import project-type --managed facets/azure --name "My Platform"
`Cloud Account (azure_provider)` `Network/VNet (azure_network)` `AKS (aks)` `Node Pool (azure)` `Service (azure)` `Workload Identity (azure)`
**Managed Datastores**
-`PostgreSQL/Flexible Server (azure-flexible-server)` `MySQL/Flexible Server (azure-flexible-server)` `MongoDB/Cosmos DB (cosmosdb)` `Redis/Azure Cache (azure_cache_custom)`
+`PostgreSQL/Flexible Server (azure-flexible-server)` `PostgreSQL/Logical (logical)` `MySQL/Flexible Server (azure-flexible-server)` `MongoDB/Cosmos DB (cosmosdb)` `Redis/Azure Cache (azure_cache_custom)`
**Self-hosted via KubeBlocks**
`PostgreSQL` `MySQL` `MongoDB` `Redis`
diff --git a/index.html b/index.html
index 1ed4eed6..6c2f6378 100644
--- a/index.html
+++ b/index.html
@@ -1133,7 +1133,7 @@
NGINX Gateway Fabric Ingress (AWS) NGINX Gateway Fabric Ingress (AWS) NGINX Gateway Fabric Ingress (AWS) :@:/
+```
+
+Credentials, host and port are inherited from the source; only the database
+path segment is replaced. When `database_name` is unset, the source's own
+`connection_string` is passed through verbatim.
+
+## Use case: staging DB consolidation
+
+In staging it is common to run a single shared physical PostgreSQL instance
+and give each service its own logical database. Model this by:
+
+1. Deploying one real postgres datastore (e.g. `aws-rds`) — the shared instance.
+2. Adding a `postgres/logical` resource per service, with `spec.source`
+ pointing (per environment, via `--flavor`/override) at the shared instance
+ and `database_name` set to the service's logical database.
+
+This keeps blueprints identical across environments while consolidating
+physical infrastructure in non-production.
diff --git a/modules/datastore/postgres/logical/1.0/facets.yaml b/modules/datastore/postgres/logical/1.0/facets.yaml
new file mode 100644
index 00000000..6a8b0663
--- /dev/null
+++ b/modules/datastore/postgres/logical/1.0/facets.yaml
@@ -0,0 +1,62 @@
+intent: postgres
+flavor: logical
+version: '1.0'
+clouds:
+- aws
+- gcp
+- azure
+description: Logical — a logical database hosted on an existing/shared postgres instance;
+ re-exposes its connection outputs (no resources created)
+intentDetails:
+ type: Datastores
+ description: A logical database hosted on an existing/shared PostgreSQL instance (no resources created)
+ displayName: PostgreSQL
+ iconUrl: https://raw.githubusercontent.com/Facets-cloud/facets-modules-redesign/main/icons/postgres.svg
+spec:
+ title: PostgreSQL Logical
+ description: A logical database hosted on an existing/shared postgres instance.
+ Re-exposes the shared instance's connection outputs and creates no cloud
+ resources. Use for staging DB consolidation, optionally overriding the
+ target database name in the connection string.
+ type: object
+ x-ui-order:
+ - source
+ - database_name
+ properties:
+ source:
+ type: object
+ title: Source PostgreSQL Datastore
+ description: The existing/shared postgres instance hosting this logical
+ database, whose connection outputs are re-exposed. Resolves to the
+ selected resource's full outputs (interfaces and attributes).
+ x-ui-output-type: '@facets/postgres'
+ database_name:
+ type: string
+ title: Logical Database Name
+ description: Optional logical database name to target on the shared host. When
+ set, the connection string is rebuilt to point at this database instead of
+ the source's default database. Leave empty to pass the source connection
+ through unchanged.
+ x-ui-overrides-only: true
+ pattern: ^[a-zA-Z][a-zA-Z0-9_]*$
+ minLength: 1
+ maxLength: 63
+ required:
+ - source
+outputs:
+ default:
+ type: '@facets/postgres'
+ title: PostgreSQL Logical
+iac:
+ validated_files:
+ - main.tf
+ - variables.tf
+ - outputs.tf
+sample:
+ kind: postgres
+ flavor: logical
+ version: '1.0'
+ disabled: true
+ spec:
+ source: {}
+ database_name: app_logical
diff --git a/modules/datastore/postgres/logical/1.0/main.tf b/modules/datastore/postgres/logical/1.0/main.tf
new file mode 100644
index 00000000..f6ef5e73
--- /dev/null
+++ b/modules/datastore/postgres/logical/1.0/main.tf
@@ -0,0 +1,17 @@
+# PostgreSQL Logical flavour
+#
+# This flavour creates NO cloud resources and declares NO providers.
+# It is a pure passthrough: it re-exposes the connection outputs of an
+# existing postgres datastore (selected via spec.source), optionally
+# re-targeting the connection string at a different logical database.
+#
+# Use case: staging DB consolidation, where a logical database points at a
+# shared physical instance. The source datastore is selected per-environment
+# (override-able), so the same blueprint resource can fan out to different
+# shared instances across environments.
+
+terraform {
+ required_version = ">= 1.0"
+}
+
+# Intentionally no resources.
diff --git a/modules/datastore/postgres/logical/1.0/outputs.tf b/modules/datastore/postgres/logical/1.0/outputs.tf
new file mode 100644
index 00000000..1b8e2a00
--- /dev/null
+++ b/modules/datastore/postgres/logical/1.0/outputs.tf
@@ -0,0 +1,52 @@
+locals {
+ # Full outputs of the referenced postgres datastore.
+ source_interfaces = var.instance.spec.source.interfaces
+ source_reader = local.source_interfaces.reader
+ source_writer = local.source_interfaces.writer
+
+ # Optional logical database to re-target the connection string at.
+ override_db = lookup(var.instance.spec, "database_name", null)
+
+ # Rebuild the connection string against the overridden logical database when
+ # one is supplied, otherwise pass the source connection string through.
+ reader_connection_string = local.override_db != null ? format(
+ "postgres://%s:%s@%s:%s/%s",
+ local.source_reader.username,
+ local.source_reader.password,
+ local.source_reader.host,
+ local.source_reader.port,
+ local.override_db,
+ ) : local.source_reader.connection_string
+
+ writer_connection_string = local.override_db != null ? format(
+ "postgres://%s:%s@%s:%s/%s",
+ local.source_writer.username,
+ local.source_writer.password,
+ local.source_writer.host,
+ local.source_writer.port,
+ local.override_db,
+ ) : local.source_writer.connection_string
+
+ # Passthrough of any source attributes (e.g. db_instance_identifier, arn)
+ # when the source datastore emits them.
+ output_attributes = lookup(var.instance.spec.source, "attributes", {})
+
+ output_interfaces = {
+ reader = {
+ host = local.source_reader.host
+ port = local.source_reader.port
+ username = local.source_reader.username
+ password = local.source_reader.password
+ connection_string = local.reader_connection_string
+ secrets = ["password", "connection_string"]
+ }
+ writer = {
+ host = local.source_writer.host
+ port = local.source_writer.port
+ username = local.source_writer.username
+ password = local.source_writer.password
+ connection_string = local.writer_connection_string
+ secrets = ["password", "connection_string"]
+ }
+ }
+}
diff --git a/modules/datastore/postgres/logical/1.0/variables.tf b/modules/datastore/postgres/logical/1.0/variables.tf
new file mode 100644
index 00000000..023e5785
--- /dev/null
+++ b/modules/datastore/postgres/logical/1.0/variables.tf
@@ -0,0 +1,54 @@
+variable "instance" {
+ description = "A logical database hosted on an existing/shared PostgreSQL instance — re-exposes its connection outputs (no resources created)"
+ type = object({
+ kind = string
+ flavor = string
+ version = string
+ spec = object({
+ # Resolves to the selected postgres resource's FULL outputs
+ # (interfaces + attributes) injected by the platform via x-ui-output-type.
+ source = object({
+ attributes = optional(any, {})
+ interfaces = object({
+ reader = object({
+ host = string
+ port = string
+ username = string
+ password = string
+ connection_string = string
+ secrets = optional(list(string), [])
+ })
+ writer = object({
+ host = string
+ port = string
+ username = string
+ password = string
+ connection_string = string
+ secrets = optional(list(string), [])
+ })
+ })
+ })
+ # Optional logical DB name to target on the shared host (override-only).
+ database_name = optional(string)
+ })
+ })
+}
+
+variable "instance_name" {
+ description = "The architectural name for the resource as added in the Facets blueprint designer."
+ type = string
+}
+
+variable "environment" {
+ description = "An object containing details about the environment."
+ type = object({
+ name = string
+ unique_name = string
+ cloud_tags = map(string)
+ })
+}
+
+variable "inputs" {
+ description = "A map of inputs requested by the module developer. This logical flavour provisions nothing and requires no inputs."
+ type = object({})
+}
diff --git a/project-type/aws/project-type.yml b/project-type/aws/project-type.yml
index a9f35545..a4756343 100644
--- a/project-type/aws/project-type.yml
+++ b/project-type/aws/project-type.yml
@@ -62,6 +62,8 @@ modules:
flavor: aws-aurora
- intent: postgres
flavor: aws-rds
+ - intent: postgres
+ flavor: logical
- intent: redis
flavor: aws-elasticache
- intent: keda
diff --git a/project-type/azure/project-type.yml b/project-type/azure/project-type.yml
index 555b5009..8eec68ae 100644
--- a/project-type/azure/project-type.yml
+++ b/project-type/azure/project-type.yml
@@ -34,6 +34,8 @@ modules:
flavor: azure-flexible-server
- intent: postgres
flavor: azure-flexible-server
+ - intent: postgres
+ flavor: logical
- intent: redis
flavor: azure_cache_custom
diff --git a/project-type/gcp/project-type.yml b/project-type/gcp/project-type.yml
index c5c253dd..80fc0e26 100644
--- a/project-type/gcp/project-type.yml
+++ b/project-type/gcp/project-type.yml
@@ -53,6 +53,8 @@ modules:
flavor: gcp-cloudsql
- intent: postgres
flavor: gcp-cloudsql
+ - intent: postgres
+ flavor: logical
- intent: redis
flavor: gcp-memorystore
# - intent: kafka_topic