In order to isolate your production from the "dirty" master branch, I'm suggesting to use a simple release/delivery model. First, you let everybody commit to the "master" branch, of course, after they pass all unit/integration tests and the entire merge pipeline. Then, you create a candidate branch, which you deploy to the staging environment and let your testers break it as much as they can. Then, when it becomes obvious that testers can't find anything critical there, you the "candidate" branch into the "live" branch and deploy to production, via the deployment pipeline. You may have a number of release candidates, staying in testing simultaneously. This model proved its validity in many projects I've been doing over the last years. If you tried it and there were problems, please let me know in the comments.