GitOps ArgoCD Crossplane: Manage Everything Through Pull Requests
Imagine this: a developer opens a pull request that adds a new microservice. The PR includes the application code, Kubernetes manifests, and a database definition. When the PR is merged, the service is automatically deployed, the database is provisioned, and monitoring is configured — all without anyone running kubectl or clicking through a cloud console. That’s GitOps ArgoCD Crossplane in practice.
GitOps: Git as the Source of Truth
GitOps is simple in principle: everything that defines your system — application configurations, infrastructure definitions, networking rules, access policies — lives in Git. A tool continuously compares what Git says the system should look like with what the system actually looks like, and corrects any drift.
This gives you capabilities that manual management can’t match:
- Audit trail: Every change is a Git commit with an author, timestamp, and description. You can answer “who changed what, when, and why?” for any point in history.
- Rollback: Reverting a bad deployment is
git revert. The GitOps tool sees the reverted state and automatically rolls back the live system. - Code review for infrastructure: Infrastructure changes go through the same PR review process as application code. A junior engineer’s Kubernetes manifest change gets reviewed by a senior before applying.
- Disaster recovery: If your entire cluster dies, point ArgoCD at your Git repository and it recreates everything. The repository IS your system definition.
The principle that ties these together is declarative reconciliation. You never tell the system how to get to a state; you describe the desired end state and a controller closes the gap continuously. This is the same control-loop pattern Kubernetes itself uses internally, which is why GitOps feels native on Kubernetes rather than bolted on. The OpenGitOps project formalizes this into four principles — declarative, versioned and immutable, pulled automatically, and continuously reconciled — and they are a useful checklist when evaluating whether a workflow is genuinely GitOps or just “Git plus a deploy script.”
ArgoCD: Application Delivery
ArgoCD watches your Git repository and synchronizes the Kubernetes cluster to match. When you push a new container image tag to your manifests repository, ArgoCD detects the change and applies it. If someone manually modifies a deployment (kubectl edit), ArgoCD detects the drift and can auto-correct it.
# argocd/applications/order-service.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
namespace: argocd
# Finalizer ensures ArgoCD cleans up resources when the app is deleted
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: production
source:
repoURL: https://github.com/company/k8s-manifests
path: services/order-service/overlays/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: commerce
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Correct manual changes automatically
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- ApplyOutOfSyncOnly=true # Only apply changed resources
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Health checks — ArgoCD waits for resources to be healthy
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # Let HPA control replicas, not Git
The ignoreDifferences section is critical. Without it, ArgoCD would fight with your Horizontal Pod Autoscaler — ArgoCD wants 3 replicas (from Git), HPA wants 7 replicas (based on load). The ignoreDifferences tells ArgoCD to let HPA control the replica count while managing everything else.
Sync Waves and Hooks: Controlling Deployment Order
Real deployments are rarely a flat set of resources that can all apply at once. A database migration must finish before the new application version starts; a CRD must exist before any custom resource that uses it. ArgoCD solves ordering with sync waves — an annotation that groups resources into ordered phases. ArgoCD applies wave 0 fully and waits for it to become healthy before touching wave 1.
# A Job that runs schema migrations BEFORE the app rolls out
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/sync-wave: "-1" # runs before wave 0
argocd.argoproj.io/hook: PreSync # treat as a pre-sync hook
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: ghcr.io/company/order-service:1.8.0
command: ["./migrate", "up"]
---
# The Deployment waits for the migration (lower wave) to complete first
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
annotations:
argocd.argoproj.io/sync-wave: "0"
Without sync waves, ArgoCD might start the new pods before the migration finishes, and they would crash-loop against a schema they expect to exist. With them, the rollout is deterministic. A common pattern is to reserve negative waves for prerequisites (CRDs, migrations, secrets), wave 0 for core workloads, and positive waves for things that depend on the app being up, such as smoke-test jobs or cache warmers.
Crossplane: Infrastructure as Kubernetes Resources
Crossplane extends Kubernetes with custom resources that represent cloud infrastructure. Instead of writing Terraform and running it separately, you define your database, cache, and queue as Kubernetes resources right alongside your application manifests. ArgoCD deploys them all together.
# infrastructure/databases/order-service-db.yaml
# This creates a REAL RDS instance on AWS — managed through Kubernetes
apiVersion: database.company.io/v1
kind: PostgreSQLInstance
metadata:
name: order-service-db
namespace: commerce
spec:
parameters:
size: medium # Maps to: db.r6g.large, 100GB, Multi-AZ
version: "17"
backup:
retentionDays: 30
window: "03:00-04:00"
# Connection details automatically written to a Kubernetes Secret
writeConnectionSecretToRef:
name: order-service-db-credentials
namespace: commerce
---
# The application automatically picks up the database credentials
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
containers:
- name: api
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: order-service-db-credentials
key: endpoint
Why this is powerful: The database definition lives in the same repository as the application that uses it. When a developer creates a new service from a template, the database is included. When the service is deleted, the database definition is removed and Crossplane deprovisions it. Moreover, the database credentials flow automatically through Kubernetes Secrets — no manual credential management.
Compositions: The Platform Abstraction Layer
The clean PostgreSQLInstance resource above does not come for free — a platform team builds it using Crossplane’s Composite Resource Definition (XRD) and Composition. The XRD defines the simplified API that developers see (just size, version, and backup), while the Composition maps that to the dozens of low-level managed resources the cloud provider actually requires: the RDS instance, a subnet group, a parameter group, a security group, and an IAM role.
This separation is the whole point. Developers should not need to know that size: medium means a db.r6g.large across two availability zones with encryption at rest enabled — that policy belongs to the platform team, encoded once in the Composition and enforced everywhere. Because the Composition is itself a Kubernetes resource stored in Git, your infrastructure standards are versioned and reviewed exactly like everything else. When compliance requires that all databases enable encryption and 30-day backups, you change one Composition and every future database inherits it.
GitOps ArgoCD Crossplane: The Unified Workflow
Here’s how it all works together in a real deployment:
- Developer opens a PR adding a new service: application code, Kubernetes manifests, and a Crossplane database definition
- CI pipeline runs tests, builds the container image, and pushes it to the registry
- PR is reviewed and merged to main
- ArgoCD detects the change in the manifests repository
- ArgoCD creates the Kubernetes Deployment, Service, and Ingress
- ArgoCD creates the Crossplane PostgreSQLInstance resource
- Crossplane provisions the RDS instance on AWS (takes 5-10 minutes)
- Crossplane writes connection credentials to a Kubernetes Secret
- The application pod starts and connects to the database using the Secret
- ArgoCD reports the application as Healthy and Synced
All of this happens automatically from a single PR merge. No kubectl commands, no AWS console, no manual credential management.
Multi-Cluster Management
ArgoCD ApplicationSets generate applications dynamically across multiple clusters. Define a template once, and it deploys to dev, staging, and production with environment-specific overrides. Furthermore, Crossplane Compositions abstract cloud-specific details — the same PostgreSQLInstance resource works on AWS (RDS), GCP (Cloud SQL), or Azure (Azure Database) by swapping the underlying provider.
Getting Started: Step by Step
- Week 1: Install ArgoCD on your cluster. Move one existing service’s manifests to a Git repository. Create an ArgoCD Application pointing to it.
- Week 2: Enable automated sync and self-heal. Test: manually change a deployment and watch ArgoCD correct it.
- Week 3: Install Crossplane with your cloud provider. Create a Composition for your most common database type. Test provisioning through Git.
- Week 4: Create an ApplicationSet for your multi-environment setup. Migrate a second service to GitOps.
When NOT to Use This Stack: Honest Trade-offs
This architecture is genuinely powerful, but it is not free, and a small team can drown in its operational surface. Running ArgoCD and Crossplane means you now operate two more controllers, keep their CRDs upgraded, and debug reconciliation loops when something gets stuck. For a team running three services on a single cluster, that overhead may outweigh the benefit; a simpler push-based pipeline can be the right call until scale justifies the platform.
Managing stateful cloud infrastructure through Crossplane carries a specific, sharp risk worth naming: a misconfigured prune: true sync policy or an accidental deletion of a Composite Resource can trigger Crossplane to deprovision a production database. Terraform at least forces a human to read a destroy plan; a GitOps reconciler acts the moment Git changes. Mitigate this with deletion-protection annotations, the Orphan deletion policy on critical resources, and out-of-band backups — never rely on the reconciler alone to protect data.
There are also maturity gaps. Crossplane’s provider coverage, while broad, still lags Terraform for niche or brand-new cloud services, so you may hit a resource that simply has no managed equivalent yet. Drift between Crossplane’s view and reality can occur when someone edits infrastructure in the cloud console, and reconciling that can be fiddly. In short, adopt this stack when you have enough services and enough engineers that self-service and consistency genuinely pay off — and protect your stateful resources deliberately before you turn on automated pruning.
Related Reading:
- Platform Engineering Developer Portal
- OpenTofu Terraform Migration Guide
- Kubernetes Pod Resizing Guide
Resources:
In conclusion, GitOps ArgoCD Crossplane unifies application and infrastructure management through Git. Every change is reviewed, auditable, and reversible. Start with ArgoCD for application delivery, add Crossplane for infrastructure, and build toward a fully self-service developer platform.