Orchestration

GitOps: From Dev to Prod Automation

August 1, 2026 Kubezilla Team 2 min read

Navigating the complexities of modern application deployment across multiple environments—from development to staging and ultimately production—can be a daunting task. Inconsistent configurations, manual errors, and a lack of transparency often lead to “it works on my machine” syndrome, delayed releases, and production incidents. This is where GitOps shines, offering a declarative, version-controlled approach to managing infrastructure and applications. By treating Git as the single source of truth, GitOps automates the synchronization of desired state from Git repositories to your Kubernetes clusters, ensuring consistency, auditability, and faster deployments.

This guide will demystify the process of implementing a multi-environment GitOps workflow, focusing on promoting applications seamlessly from development to production. We’ll leverage powerful tools like Argo CD for continuous delivery and Helm for packaging, demonstrating how to structure your repositories, manage environment-specific configurations, and establish robust promotion pipelines. Get ready to transform your deployment strategy from a chaotic manual dance to a streamlined, automated, and reliable journey.

TL;DR: Multi-Environment GitOps with Argo CD & Helm

Automate your Dev-to-Prod deployments using Git as the single source of truth. Structure your Git repositories for environments, use Helm for packaging, and Argo CD for synchronization. Key steps:

  • Set up Git Repos: Separate application (source code), Helm chart, and environment configuration (GitOps) repos.
  • Install Argo CD: Deploy Argo CD to your cluster(s) to manage deployments.
  • Create Helm Charts: Package your application with Helm, using values.yaml for defaults.
  • Configure Environments: Use Kustomize or Helm overrides in your GitOps repo for environment-specific settings.
  • Define Argo CD Applications: Point Argo CD to your GitOps repo to sync configurations for each environment.
  • Promote Changes: Merge changes from dev to staging to prod branches in your GitOps repo.

# Install Argo CD CLI
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64

# Install Argo CD to your cluster
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Access Argo CD UI (get initial password)
kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d; echo

# Example: Create an Argo CD Application for dev
kubectl apply -f - <

Prerequisites

Before diving into the implementation, ensure you have the following:

  • Kubernetes Cluster: Access to a Kubernetes cluster (e.g., Kind, Minikube, or a cloud provider's managed service like AWS EKS, GCP GKE, or Azure AKS).
  • kubectl: The Kubernetes command-line tool, configured to connect to your cluster. Refer to the official Kubernetes documentation for installation.
  • helm: The Helm CLI for managing Kubernetes applications. Install it from the Helm website.
  • git: Version control system installed and configured.
  • GitHub/GitLab/Bitbucket Account: A Git hosting service to store your repositories.
  • Basic Kubernetes Knowledge: Familiarity with Deployments, Services, Namespaces, and basic YAML syntax.
  • Basic Helm Knowledge: Understanding of Helm charts, values, and releases.

Step-by-Step Guide

Step 1: Repository Structure Design

A well-defined repository structure is the cornerstone of effective multi-environment GitOps. We'll adopt a common pattern: separating application code, Helm charts, and environment-specific configurations into distinct repositories. This separation of concerns allows for independent development, packaging, and deployment pipeline management.

The application repository holds your source code and CI pipeline. The Helm chart repository contains the generic, reusable Helm chart for your application. Finally, the GitOps repository will house all environment-specific configurations, including Helm values.yaml overrides and Argo CD Application definitions. This structure facilitates clear ownership and prevents configuration drift between environments.


# Example repository structure
#
# 1. Application Repository (e.g., myapp-src)
#    ├── src/
#    ├── Dockerfile
#    └── .github/workflows/ci.yaml (builds image, pushes to registry)
#
# 2. Helm Chart Repository (e.g., myapp-helm-chart)
#    └── my-app/
#        ├── Chart.yaml
#        ├── values.yaml (default values)
#        ├── templates/
#        └── ...
#
# 3. GitOps Repository (e.g., myapp-gitops-config)
#    ├── environments/
#    │   ├── dev/
#    │   │   ├── my-app/
#    │   │   │   ├── kustomization.yaml (optional, for overlays)
#    │   │   │   └── values.yaml (dev-specific overrides)
#    │   │   └── argo-app.yaml (Argo CD Application definition for dev)
#    │   ├── staging/
#    │   │   ├── my-app/
#    │   │   │   └── values.yaml (staging-specific overrides)
#    │   │   └── argo-app.yaml (Argo CD Application definition for staging)
#    │   └── prod/
#    │       ├── my-app/
#    │       │   └── values.yaml (prod-specific overrides)
#    │       └── argo-app.yaml (Argo CD Application definition for prod)
#    └── README.md

Verify: You should have three distinct (empty for now) Git repositories created in your Git hosting service (e.g., GitHub).

Step 2: Install Argo CD

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. It automates the deployment of your applications by continuously monitoring your Git repositories for changes and synchronizing them with your cluster. Installing Argo CD is straightforward, and it typically runs within its own namespace in your Kubernetes cluster.

This step involves creating the necessary Kubernetes resources for Argo CD, including Deployments, Services, and RBAC roles. Once installed, you'll be able to access its intuitive web UI and CLI to manage your applications. For enhanced security, you might consider integrating Argo CD with Sigstore and Kyverno to ensure only signed images are deployed.


# Create the argocd namespace
kubectl create namespace argocd

# Apply the Argo CD installation manifests
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for Argo CD pods to be ready
echo "Waiting for Argo CD pods to be ready..."
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-server -n argocd --timeout=300s
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-repo-server -n argocd --timeout=300s
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-application-controller -n argocd --timeout=300s

# Get the initial admin password for the Argo CD UI
echo "Argo CD initial admin password:"
kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d; echo

# Expose Argo CD UI (using port-forward for local access, use Ingress/LoadBalancer for production)
echo "Port-forwarding Argo CD UI to http://localhost:8080"
kubectl port-forward svc/argocd-server -n argocd 8080:443 &

Verify:

1. Open your browser and navigate to https://localhost:8080 (or the appropriate Ingress/LoadBalancer URL).

2. Log in with username admin and the password retrieved from the secret.

3. You should see the Argo CD UI, initially empty of applications.

4. Check Argo CD pods status:


kubectl get pods -n argocd

NAME                                                READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                     1/1     Running   0          2m
argocd-dex-server-79c6d8778f-6wz6l                  1/1     Running   0          2m
argocd-notifications-controller-747d69b4c6-2c974    1/1     Running   0          2m
argocd-repo-server-75c6d668c6-2z79c                 1/1     Running   0          2m
argocd-server-84f7b6b4d-h6l4x                       1/1     Running   0          2m
argocd-redis-576f7f6874-ggh7p                       1/1     Running   0          2m

Step 3: Create a Helm Chart for Your Application

Helm charts are essential for packaging, sharing, and deploying Kubernetes applications. They provide a templating mechanism to define Kubernetes resources, along with default values that can be overridden for different environments. This step involves creating a basic Helm chart for a simple Nginx application.

The chart will define a Deployment and a Service. Later, we'll use our GitOps repository to provide environment-specific values.yaml files that override these defaults, allowing us to customize image tags, replica counts, and other parameters for development, staging, and production environments. For more advanced networking configurations, you might consider exploring Kubernetes Gateway API.


# Clone your Helm chart repository (or create a new one)
git clone https://github.com/your-org/myapp-helm-chart.git
cd myapp-helm-chart

# Create a new Helm chart
helm create my-app
cd my-app

# Modify values.yaml to be generic
cat < values.yaml
replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent
  # Overwrite the default tag to ensure we use a specific version
  tag: "1.23.3" 

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  className: ""
  annotations: {}
  host: chart-example.local
  paths:
    - path: /
      pathType: ImplementationSpecific
EOF

# Commit and push the Helm chart
git add .
git commit -m "Initial Helm chart for my-app"
git push origin main

Verify:

1. Your myapp-helm-chart repository should now contain the my-app Helm chart with the updated values.yaml.

2. You can test render the chart locally:


helm template my-app .

This command should output the Kubernetes YAML resources generated by the chart with default values.

Step 4: Configure Environment-Specific Overrides in GitOps Repo

Now, let's populate our GitOps repository with environment-specific configurations. We'll create branches for dev, staging, and prod, each containing a directory for our application and an Argo CD Application definition. The core idea is to define overrides for our Helm chart's values.yaml that are unique to each environment.

This approach allows us to manage different replica counts, image tags, resource limits, and other parameters per environment, all version-controlled in Git. The promotion process will then simply involve merging changes between these branches. For example, a development environment might run with fewer replicas and less stringent resource limits than production. You can also define environment-specific Network Policies here for granular security.


# Clone your GitOps repository
git clone https://github.com/your-org/myapp-gitops-config.git
cd myapp-gitops-config

# Create 'dev' branch and directory
git checkout -b dev
mkdir -p environments/dev/my-app

# Create dev-specific values.yaml
cat < environments/dev/my-app/values.yaml
replicaCount: 1
image:
  tag: "1.23.3" # Use a specific tag for dev
service:
  type: ClusterIP # Or NodePort for easy local access
ingress:
  enabled: true
  className: nginx
  host: dev.my-app.kubezilla.io
EOF

# Create the Argo CD Application definition for dev
cat < environments/dev/argo-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-dev
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/myapp-helm-chart.git # Your Helm chart repo
    targetRevision: main # Or a specific chart version
    path: my-app # Path to the chart within the repo
    helm:
      valueFiles:
        - ../../environments/dev/my-app/values.yaml # Path to the environment-specific values
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp-dev # Deploy to a dedicated dev namespace
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true # Argo CD will create the namespace if it doesn't exist
EOF

git add .
git commit -m "Add dev environment configuration and Argo CD app"
git push origin dev

# Create 'staging' branch and directory (inheriting from dev)
git checkout -b staging
cp environments/dev/my-app/values.yaml environments/staging/my-app/values.yaml
cp environments/dev/argo-app.yaml environments/staging/argo-app.yaml
sed -i 's/myapp-dev/myapp-staging/g' environments/staging/argo-app.yaml
sed -i 's/myapp-dev/myapp-staging/g' environments/staging/argo-app.yaml
sed -i 's/dev.my-app.kubezilla.io/staging.my-app.kubezilla.io/g' environments/staging/my-app/values.yaml
sed -i 's/tag: "1.23.3"/tag: "1.23.3"/g' environments/staging/my-app/values.yaml # Update image tag if needed

git add .
git commit -m "Add staging environment configuration and Argo CD app"
git push origin staging

# Create 'prod' branch and directory (inheriting from staging)
git checkout -b prod
cp environments/staging/my-app/values.yaml environments/prod/my-app/values.yaml
cp environments/staging/argo-app.yaml environments/prod/argo-app.yaml
sed -i 's/myapp-staging/myapp-prod/g' environments/prod/argo-app.yaml
sed -i 's/myapp-staging/myapp-prod/g' environments/prod/argo-app.yaml
sed -i 's/staging.my-app.kubezilla.io/prod.my-app.kubezilla.io/g' environments/prod/my-app/values.yaml
sed -i 's/replicaCount: 1/replicaCount: 3/g' environments/prod/my-app/values.yaml # More replicas for prod
sed -i 's/tag: "1.23.3"/tag: "1.23.3"/g' environments/prod/my-app/values.yaml # Update image tag if needed

git add .
git commit -m "Add prod environment configuration and Argo CD app"
git push origin prod

# Go back to main branch
git checkout main

Verify:

1. Your myapp-gitops-config repository should now have dev, staging, and prod branches.

2. Each branch should contain the respective environments//my-app/values.yaml and environments//argo-app.yaml files.

3. Check the content of the values.yaml files to ensure they have environment-specific overrides.

Step 5: Register Argo CD Applications for Each Environment

With our GitOps repository structured and populated, we can now tell Argo CD about our applications. We'll register the Argo CD Application definitions for our dev, staging, and prod environments. Each definition will point to the respective branch in our GitOps repository, instructing Argo CD to synchronize the Helm chart with its environment-specific overrides.

Argo CD will continuously monitor these Git branches. Any changes merged into, for example, the dev branch will be automatically detected and applied to the myapp-dev namespace. This automation is the core of GitOps, ensuring that your cluster state always reflects the desired state in Git. You can use eBPF Observability with Hubble to monitor the network traffic of these deployments.


# Ensure you are in the myapp-gitops-config directory
cd myapp-gitops-config

# Apply the Argo CD Application definition for the 'dev' environment
# This tells Argo CD to monitor the 'dev' branch of your GitOps repo
# and deploy 'my-app' using its Helm chart and dev-specific values.
kubectl apply -n argocd -f environments/dev/argo-app.yaml

# Apply the Argo CD Application definition for the 'staging' environment
kubectl apply -n argocd -f environments/staging/argo-app.yaml

# Apply the Argo CD Application definition for the 'prod' environment
kubectl apply -n argocd -f environments/prod/argo-app.yaml

Verify:

1. Open the Argo CD UI (https://localhost:8080). You should now see three applications: myapp-dev, myapp-staging, and myapp-prod.

2. Initially, they might be in a "Missing" or "OutOfSync" state. Click on each application and manually trigger a "Sync" to deploy them for the first time.

3. After syncing, all three applications should eventually show a "Healthy" and "Synced" status.

4. Check the deployed resources in your Kubernetes cluster:


kubectl get all -n myapp-dev
kubectl get all -n myapp-staging
kubectl get all -n myapp-prod

You should see Nginx deployments and services in each namespace, with the replica counts matching your environment-specific values.yaml files (1 for dev/staging, 3 for prod).

Step 6: Promote Changes from Dev to Prod

The true power of multi-environment GitOps lies in its promotion mechanism. Instead of complex CI/CD pipelines pushing artifacts, promotion becomes a simple Git merge operation. When an application version is validated in development, you merge its configuration changes from the dev branch to the staging branch. After successful testing in staging, you merge to prod.

This process leverages Git's inherent versioning and audit trail. Every promotion is a Git commit, providing full traceability of who changed what, when, and why. Argo CD, constantly monitoring these branches, will automatically detect the merged changes and apply them to the respective clusters, ensuring a seamless and reliable rollout. Consider using Cilium WireGuard Encryption to secure traffic between your application pods, especially in production.


# Example: Update the Nginx image tag for all environments
# First, update the Helm chart's image tag in myapp-helm-chart repository
# (This simulates a new application version being built and pushed)
# For this example, we'll directly edit the dev values.yaml to simulate
# a change that needs to be promoted. In a real scenario, you'd update
# the Helm chart, then reference the new chart version or image tag.

# Make a change in the 'dev' branch of the GitOps repo
cd myapp-gitops-config
git checkout dev

# Simulate a new image tag available for dev
sed -i 's/tag: "1.23.3"/tag: "1.24.0"/g' environments/dev/my-app/values.yaml
git add environments/dev/my-app/values.yaml
git commit -m "Update Nginx image to 1.24.0 in dev"
git push origin dev

echo "Waiting for Argo CD to sync dev environment..."
sleep 30 # Give Argo CD some time to detect and sync

# Verify dev is updated
echo "Dev deployment image version:"
kubectl get deploy -n myapp-dev my-app -o jsonpath="{.spec.template.spec.containers[0].image}"

# Promote to staging
git checkout staging
git merge dev --no-edit # Merge changes from dev to staging
git push origin staging

echo "Waiting for Argo CD to sync staging environment..."
sleep 30

# Verify staging is updated
echo "Staging deployment image version:"
kubectl get deploy -n myapp-staging my-app -o jsonpath="{.spec.template.spec.containers[0].image}"

# Promote to production
git checkout prod
git merge staging --no-edit # Merge changes from staging to prod
git push origin prod

echo "Waiting for Argo CD to sync prod environment..."
sleep 30

# Verify prod is updated
echo "Prod deployment image version:"
kubectl get deploy -n myapp-prod my-app -o jsonpath="{.spec.template.spec.containers[0].image}"

Verify:

1. In the Argo CD UI, observe the myapp-dev application. It should detect the change, become "OutOfSync," and then automatically "Sync" to "Healthy" with the new image tag.

2. Repeat the observation for myapp-staging and myapp-prod as you merge changes.

3. Confirm the image tags in the respective Kubernetes namespaces using kubectl get deploy -n <namespace> my-app -o jsonpath="{.spec.template.spec.containers[0].image}". All should eventually show nginx:1.24.0.

Production Considerations

Implementing GitOps in production requires careful planning beyond the basic setup:

  • Repository Security: Protect your GitOps repository with branch protection rules, mandatory code reviews, and proper access controls. Integrate with your identity provider.
  • Secrets Management: Never commit sensitive data directly to Git. Use Kubernetes secrets managers like External Secrets Operator, Secrets Store CSI Driver with cloud vaults (e.g., AWS Secrets Manager, GCP Secret Manager, Azure Key Vault), or HashiCorp Vault.
  • Image Pull Policy & Tags: Always use specific, immutable image tags (e.g., nginx:1.24.0 or a digest nginx@sha256:...) in production to ensure deterministic deployments. Avoid latest.
  • Argo CD High Availability: For production, deploy Argo CD in a highly available configuration with multiple replicas for its components and a persistent Redis instance. Refer to the Argo CD HA documentation.
  • Monitoring and Alerting: Integrate Argo CD with your monitoring stack (Prometheus, Grafana). Set up alerts for application health, sync failures, and reconciliation errors. Consider eBPF Observability with Hubble for deep network insights.
  • Rollback Strategy: GitOps inherently supports rollbacks by reverting Git commits. Ensure your team is proficient with Git revert operations. Argo CD also provides rollback capabilities within its UI.
  • Resource Limits & Requests: Define appropriate CPU and memory limits/requests for your application pods in Helm values to ensure stability and efficient resource utilization. This is crucial for cost optimization, especially when using tools like Karpenter.
  • Network Policies: Implement strict Kubernetes Network Policies in production environments to isolate applications and restrict traffic flows.
  • Testing Strategy: Integrate automated testing (unit, integration, end-to-end) into your CI pipeline before changes are merged into environment branches. Use dedicated staging environments for thorough validation.
  • Audit Trails: Leverage Git's commit history and Argo CD's event logs for comprehensive audit trails, crucial for compliance and debugging.

Troubleshooting

  1. Issue: Argo CD Application stuck in "OutOfSync" state.

    Explanation: This typically means the live state in the cluster does not match the desired state in Git. This can happen if manual changes were made to the cluster, or if the Helm chart rendered incorrectly.

    Solution:

    1. Check the Argo CD UI for details on the "OutOfSync" resources. It will highlight the differences.

    2. If manual changes were made, you can click "Sync" and enable "Prune" to revert the cluster to the Git state.

    3. If the Helm chart is the issue, check the rendered YAML in the UI's "Manifest" tab for errors or unexpected values.

    4. Ensure your Helm chart's values.yaml paths are correct in the Argo CD Application definition.

  2. Issue: Argo CD Application stuck in "Missing" state.

    Explanation: This usually indicates that Argo CD cannot find the application's definition in the specified Git repository path or that the repository is inaccessible.

    Solution:

    1. Verify the repoURL, targetRevision, and path in your Argo CD Application YAML.

    2. Ensure Argo CD has access to the Git repository (e.g., if it's a private repo, add SSH keys or HTTPS credentials to Argo CD).

    3. Check Argo CD repo-server logs: kubectl logs -l app.kubernetes.io/name=argocd-repo-server -n argocd.

  3. Issue: Helm chart fails to render or deploy.

    Explanation: Syntax errors in values.yaml, incorrect template logic, or invalid Kubernetes resource definitions can cause Helm to fail.

    Solution:

    1. Test the Helm chart locally before pushing: helm template my-app ./my-app --values environments/dev/my-app/values.yaml (adjust paths).

    2. Use helm lint ./my-app to catch common chart issues.

    3. Check the Argo CD UI for detailed error messages during the sync process.

    4. Validate generated YAML with kubectl dry-run --validate -f <generated-yaml>.

  4. Issue: ImagePullBackOff or ErrImagePull errors.

    Explanation: The Kubernetes cluster cannot pull the specified container image. Common causes include incorrect image name/tag, private registry authentication issues, or network problems.

    Solution:

    1. Double-check the image name and tag in your values.yaml.

    2. If using a private registry, ensure imagePullSecrets are correctly configured in the Deployment and referenced in the service account.

    3. Verify connectivity to the image registry from your cluster nodes.

    4. Check pod events: kubectl describe pod <pod-name> -n <namespace>.

  5. Issue: Applications not updating after Git merge.

    Explanation: Argo CD might not be detecting changes in the Git repository or its webhook/polling mechanism is not working correctly.

    Solution:

    1. Manually refresh the application in the Argo CD UI by clicking the "Refresh" button.

    2. Ensure Argo CD has permissions to read the Git repository.

    3. If using webhooks, verify the webhook is correctly configured in your Git provider and accessible by Argo CD.

    4. If polling, check the reconcile.hardTimeout and reconcile.interval settings for Argo CD.

  6. Issue: Cluster resources not cleaning up after deleting an application.

    Explanation: This can happen if resources were created outside of Helm/Argo CD's management, or if finalizers prevent deletion.

    Solution:

    1. Ensure prune: true is set in your Argo CD sync policy.

    2. Check for Kubernetes finalizers on the resources (e.g., kubectl get <resource> <name> -o yaml). You may need to manually remove finalizers if resources are stuck.

    3. If using Helm, ensure the Helm release was properly uninstalled or deleted.

FAQ Section

  1. What is the difference between GitOps and traditional CI/CD?

    Traditional CI/CD often involves a CI pipeline pushing artifacts to environments. GitOps, on the other hand, pulls changes from Git. Git is the single source of truth for desired state, and an operator (like Argo CD) continuously reconciles the cluster's actual state with the desired state in Git. This offers better auditability, consistency, and reliability.

  2. Why use separate Git repositories for application code, Helm charts, and GitOps config?

    This "mono-repo for config, poly-repo for code" approach provides separation of concerns. Developers can work on application code independently. Chart maintainers can update packaging. DevOps engineers manage environment configurations without touching application logic. This reduces blast radius, improves security, and streamlines workflows. For large organizations, a mono-repo for everything is also an option, but requires careful management.

  3. How do I manage secrets in a GitOps workflow?

    Never commit secrets directly to Git. Instead, use a dedicated secrets management

Leave a comment