My org is doing this and our main solution has been tightly coupling to Pulumi ESC. We're using GitLab, which strongly encourages feature branches/centralized repos over forks, and so that's what we do. Fork-based-workflows may have to work out a few extra details.
Our workflow at it's purest is:
1. Make a feature branch with feature/my-name/jira-123, a jira ticket being present is minimally enforced with branch policies and a basic regex.
2. The branch existence is enough to trigger a pipeline. The pipeline extracts the jira ticket and creates a stack name [jira-123].
3. A Pulumi.lab.yaml config is already part of the repo, the pipeline copies it to Pulumi.jira-123.yaml (using pulumi config cp, instead of any os-based copy command to persist any secrets that might be encrypted in it). Then a custom script looks for {{stack_name}} and replaces it with jira-123. That's the extent of app-specific configs solved/supported.
4. For this to be most useful, Pulumi.lab.yaml imports team-name/lab from Pulumi ESC which has all the standard upstream info, such as more long running/stack stuff like a VPC, shared cluster, etc. Engineers can add whatever other envs they want to this, with the confidence they'll move to their ephemeral stack that gets spawned on feature branches.
5. Every commit to the feature branch runs a new build/test/pulumi up, and if appropriate exports things like what url things can be tested on (e.g.
https://myapp-jira-123.lab.myorg.notreal)
6. When they open a merge/pull request that is the CI's queue to run even more tests. Things like actually calling to jira to validate the ticket exists, is in a valid status, and has some QA tests checked in against it are run. The PR will be blocked until those tests pass, but the engineer is free to check 'auto-merge when checks pass', and that, combined with some webhooks from various integrations to retest on any changes, lets it be mostly a set-it-and-forget-it workflow.
7. The PR checks include a pulumi preview against as many higher envs as they have specified in their CI template. We have GitLab components where they can just say how many higher envs they actually want, because our org has various pockets that grew up independently, and now some teams have a simple dev/test where others have as many as 9... we did at least get the whole org to agree what envs come first in order of importance (e.g. lab, dev, stg, prd). So, the pipeline only checks if the env is enabled, and doesn't have to do dynamic ordering.
8. Once the various checks pass that means jira workflows and regression tests have checked all the boxes that all existing tests, and any new ones have been written and passed. The CI now runs pulumi up on each environment in order of importance until finally arriving to prod. The teams can inject things like load tests in this process, but are strongly encouraged to find a way to fit that into the MR/PR checks if they can. Things failing on their way to prod is an exception, not a rule.
I want to echo what
@some-flower-64874 said earlier, which is that this workflow wouldn't work as well if we didn't have feature flags. This workflow generally applies to microservices, and they're deployed with a feature turned off. The feature orchestration is done later, and environment wide as a different pipeline with specific regression and load tests run in lower envs then promoted up.