Separation of CI and CD processes
You're basically describing GitOps. Here's how I've followed that for a containerized microservices build and deploy pipeline:

Each service repo includes it's own Dockerfile, Kubernetes manifests, and anything else needed to locally build and test the project. The base image for the Dockerfile will likely be managed from another repo but built and hosted by the organization.

CI steps are mostly externalized to a set of common libraries or templates. So if there's a Jenkinsfile or equivalent, it sets a few variables and calls the common template to build and generate the artifacts.

Artifacts include a docker image, pushed to a registry, and also the deployment manifest (compose file, Kubernetes manifest, Helm templates), along with any other variables for those manifests. These deployment manifests/variables are pushed to a separate deployment repository that includes all of the microservices' manifest. There is only one deployment repo for all of the microservices in a single application stack, so this commit becomes a fan-in step. The push to this repository is scripted/automated during the CI of the microservice repo when a pull request is approved by enough trusted people to a protected branch. This provides the needed security checks.

The deployment repo commits triggers it's own pipelines that are effectively CD and following something close to a GitOps design. This could also be a GitOps tool monitoring the repo and applying the changes, but presently I've been sticking with a single CI tool that does both, simplifying the workflow and allowing some scripting to run on the deploy. That scripting for me loads the variables that were committed, applies additional variables (e.g. which environment is this being deployed to), renders the templates (currently a Helm umbrella chart), and deploys.

Deploys to other environments becomes a pull request between branches of the deploy repository. It could also be separate repositories to enforce security controls. Importantly, rendering the manifest should point to an immutable image tag or sha hash to ensure what was pushed to dev and tested there is the same as what's pushed to production. I include these image tags in a values file that is committed to the deploy repo.


One big advantage of this, multiple microservices that interact with each other are tested together and get deployed together as a unit, all services are fanning into a single deploy repo that contains their deployment manifests and what image tag to use. There's never the situation where service A gets a new feature, but it's buggy, service B gets built depending on that new feature, and service B gets released to production before service A (a common issue when each service is built and deployed in isolated pipelines). If A is blocking the deployment of the stack, then a pull request can be submitted to the deploy repo reverting the service A commit. Then if service B passes tests against an older service A, service B gets deployed to production.