GitOps practices aren’t dependent on any specific technology. While logically GitOps is simply managing operations by Git, there are three core practices that make up a mature GitOps practice: Infrastructure-as-code (IaC), merge requests (MRs) as the request for change and system of record, and continuous integration/continuous delivery (CI/CD).
GitOps = IaC + MRs + CI/CD
Core Practice: Infrastructure-as-Code
Infrastructure-as-Code (IaC) is the practice of keeping all infrastructure configurations stored as code. GitOps uses a Git repository as the single source of truth for the definition of your infrastructure environments. By shifting your environment definitions from manual configuration to configuration by code, you gain access to an array of benefits such as version control, code collaboration, and auditability. You also unlock Git as the user interface for your infrastructure, allowing you to leverage all of the developer tooling, training, and knowledge associated with Git for your infrastructure operations.
Although IaC is a popular and well-known practice, GitOps isn’t relegated to simply infrastructure. Any operations that can be defined as code (e.g., network, policy, security) are also benefits of GitOps. In some cases, the term “X-as-Code” (XaC) can be used to encompass operations beyond infrastructure. This Refcard will use the more established term “IaC” with the understanding that we are including the entirety of the operational environment, and not simply the infrastructure.
Declarative vs. Imperative Environments
Many modern infrastructure tools such as Kubernetes, Terraform, and AWS CloudFormation work from a declarative model. An operations engineer declares the desired state as code, and the system changes itself to conform to that state via automation. For example, a Kubernetes manifest can declare the number of pods desired for a particular service. The engineer doesn’t need to write an imperative script to bring these pods up or down until the right number is achieved because Kubernetes handles this itself.
It’s the difference between saying, “I have three servers but want six, so I need to write a script to create three more services,” and simply telling the system, “There should be six servers. If there comes a point when there are too few or too many, change the state of the environment until we have the correct number.” Using declarative patterns can be very powerful within a GitOps operational model, but they aren’t a strict requirement. You can still benefit from GitOps practices if your environments are imperatively defined.
Core Practice: Using Pull Requests for Changes
It may be surprising to learn that the underlying Git version control system used to power tools such as Bitbucket, GitHub, and GitLab doesn’t include a way to request your branch be merged back into the branch it was created or forked from. This was a later advancement introduced by Git management tools. GitHub and Bitbucket use the term pull request (PR) while GitLab uses the term merge request (MR), but functionally, they perform as a central point of developer collaboration for code review and change orchestration.
Without a proper version control and branching strategy, collaboration on new changes is a frustrating endeavor. When anyone can modify a file without a way to track who made which change, it can be almost impossible to ensure the correct version is being used. A common application development workflow uses a main branch as a centralized collaboration point. Feature branches are created from the main branch, where new work is developed, and merged back into the main branch using a pull request. Leveraging this best practice for all of your infrastructure code nets you the same benefits that developers enjoy.
In an infrastructure model, the main branch represents a particular environment (e.g., dev or production), as well as the state running in that environment. Changes are proposed on a feature branch, and a PR is made to merge the changes into the main branch. This PR allows for collaboration between operations engineers for peer review, along with the development teams, security teams, and other stakeholders.
This powerful model for collaboration permits anyone to propose a change, while also allowing you to maintain compliance by limiting the number of people who can merge the changes.
Core Practice: CI/CD Automation
The final component of a robust GitOps strategy is automating all changes made to environments via CI/CD. In an ideal scenario, no manual changes are made to a GitOps-managed environment. Instead, CI/CD serves as a type of reconciliation loop. Each time a change is made, the automation tool compares the state of the environment to the source of truth defined in the Git repository. If the Git repository shows a change, the automation tool reconciles this difference by configuring the environment to match the canonical desired state.
This type of automation serves as a powerful protection against configuration drift. There are many reasons configurations can fall out of sync. Whether due to a component failure or inadvertent manual change, each time the automation runs, it overwrites the existing state with Git source of truth.
Agent vs. Agentless GitOps
A couple of different models for GitOps automation have emerged, namely Agent-based and Agentless, each with its own pros and cons. Agentless GitOps is a traditional model, also known as push-based GitOps, in which your CI/CD tool reads from your Git repository and pushes changes into your environment.
- Pro – It’s simpler and more flexible as it can be used with any type of infrastructure, from physical servers and VMs to Kubernetes clusters.
- Con – You must give your CI/CD tool access to make writes to your environment. Requiring your environment to be open to writes from the external internet can cause security and compliance issues.
Figure 1: Agentless GitOps model

This is a preview of the Essentials of GitOps Refcard. To read the entire Refcard, please download the PDF from the link above.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}