An example GitOps implementation using GitHub Enterprise Cloud features.
These are the steps executed in order to deploy a specific backend service to dev
, uat
and prod
environments:
- Engineer calls
service-release.yaml
with parameters:service
, version. service-release.yaml
builds and uploads the service to container registry.service-release.yaml
callsoverlay-update.yaml
with parameters:overlay=dev
,service
,version
.overlay-update.yaml
raises pull request to updatedev/kustomization.yaml
.overlay-update.yaml
merges pull request to updatedev/kustomization.yaml
without approvals.overlay-deploy.yaml
is triggered oncedev/kustomization.yaml
pull request is merged.overlay-deploy.yaml
deploys the service todev
environment.overlay-deploy.yaml
callsoverlay-update.yaml
with parameters:overlay=uat
,service
.overlay-update.yaml
copies the service version fromdev/kustomization.yaml
touat/kustomization.yaml
.overlay-update.yaml
raises pull request to updateuat/kustomization.yaml
.- Reviewer(s) approve and merge pull request to update
uat/kustomization.yaml
. overlay-deploy.yaml
is triggered onceuat/kustomization.yaml
pull request is merged.overlay-deploy.yaml
deploys the service touat
environment.overlay-deploy.yaml
callsoverlay-update.yaml
with parameters:overlay=prod
,service
.overlay-update.yaml
copies the service version fromuat/kustomization.yaml
toprod/kustomization.yaml
.overlay-update.yaml
raises pull request to updateprod/kustomization.yaml
.- Reviewer(s) approve and merge pull request to update
prod/kustomization.yaml
. overlay-deploy.yaml
is triggered onceprod/kustomization.yaml
pull request is merged.overlay-deploy.yaml
deploys the service toprod
environment.
Below is an example implementation in pseudocode of the steps above:
onManualDispatch(service, ref) {
service-release.yaml (service, ref)
-> calls overlay-update.yaml (overlay=dev, service, ref) # Raises PR: main <- release/<service-name>/dev
}
onPullRequestMerged(branch=release/<service-name>/dev) {
overlay-deploy.yaml
-> calls overlay-update.yaml (overlay=uat, service) # Raises PR: main <- release/<service-name>/uat
}
onPullRequestMerged(branch=release/<service-name>/uat) {
overlay-deploy.yaml
-> calls overlay-update.yaml (overlay=prod, service) # Raises PR: main <- release/<service-name>/prod
}
onPullRequestMerged(branch=release/<service-name>/prod) {
overlay-deploy.yaml
}
- Settings > General > Pull Requests > Automatically delete head branches.
- Settings > Actions > General > Workflow permissions > Read and write permissions.
- Settings > Actions > General > Workflow permissions > Allow GitHub Actions to create and approve pull requests.
- Branch Protections >
main
> Require a pull request before merging. - Branch Protections >
main
> Require status checks to pass before merging. - Branch Protections >
main
> Do not allow bypassing the above settings.
- Why not use GitHub Environment Protections for approvals instead of pull requests? Environment Protections support only a single approver per environment, while the pull request mechanism does not have this limitation and it is more flexible.
- How does this implementation scale in terms of adding new environments and services? The GitHub Actions workflow implementation is reusable across environments and services so scaling requires minimal changes.
- Add
CODEOWNERS
and define which teams can approve pull requests for each overlay. - Implement code scanning and linting steps in Dockerfile to reduce service coupling with workflow definitions.
- Limit who can call workflow_dispatch.