The most common failure with per-PR environments is not the CI job. It is the absence of a naming contract. Before any YAML is written, decide three things: what the environment is called, where it lives, and how it is found again when it must be destroyed.
The simplest contract that survives real teams is to derive everything from the pull request number. The common pattern in Kubernetes setups is one namespace per pull request, with the namespace name matching the pull request so that two developers working on "my-billing-feature" and "fix-queue-size" get two isolated namespaces with the same names the Octopus Argo CD walkthrough. That single decision gives you a resource group you can delete in one call, a label you can filter on, and a name that is stable if the PR branch is force-pushed.
For the URL there are two mainstream options. A path-based scheme (example.com/pr-123) is easy because it needs no wildcard DNS or per-preview certificates, but it only works if the application tolerates running under a different root context. A host-based scheme (pr-123.preview.example.com) is more robust but requires a wildcard DNS record and a wildcard certificate pointing at the cluster holding all preview namespaces the Kubernetes namespaces guide. Pick host-based unless you have a specific reason not to: path-based environments break quietly when a frontend hardcodes absolute asset paths, and that failure looks like an application bug rather than an infrastructure choice.
Whatever you choose, write it down once. The CI job that creates the environment and the handler that deletes it must compute the same name from the same inputs, or you will build an orphan factory.
The goal of the whole exercise is the property the twelve-factor methodology calls dev/prod parity: keep development, staging and production as similar as possible so that code that worked in testing does not fail on contact with production 12factor.net. A preview environment per PR is the most aggressive version of parity available, because it tests the change against the same backing service types that production runs, not a local approximation. The methodology explicitly warns that using SQLite locally and PostgreSQL in production produces "tiny incompatibilities" that only surface later 12factor.net.



