For our environments, we have:
• Network application that has 2 stacks, lowers (dev/test envs) and production. That ensures vnets/subnets are built to look exactly the same, but use different IP ranges per environment. This application would run VERY infrequently. Only when we wanted to add subnets probably. This application is the considered "the base" that everything else will build on
• Kubernetes application - this has 6 stacks. lowers-shared, cluster per lowers environment (dev/test/preprod), production-shared, and production. The application ensures that all clusters are structured the same, but vary based on stack configuration values. This application also installs default kubernetes resources that all clusters should have. (nginx + ingress controllers, fluentd, etc)
• LoB Applications application(s) - each LoB app that we create (microservices) are responsible for their own deployment (via a pulumi app) into the core infrastructure. All of the exported consts from the kubernetes stacks are available via stack references, so an application has a stack per target environment that shares the same name as the kubernetes cluster that it is destined to run in.
Yes, this is a lot of stacks, but it allows us to evolve the pulumi applications, and test them in a specific environment, and also to test various configuration changes in specific environments, and then we migrate those changes up through all environments, testing as we go.