GitOpsHQ Docs
Deployment

Env Compare & Bulk Ops

Compare configurations across environments and perform bulk image updates across tenants.

Overview

As projects grow in complexity — more tenants, more environments, more services — keeping configurations consistent and performing coordinated updates becomes a challenge. GitOpsHQ provides two tools to address this:

  1. Environment Compare — Side-by-side comparison of configurations across environments
  2. Bulk Image Update — Coordinated image tag changes across multiple tenants and environments

Both features work alongside the webhook system for CI/CD automation.


Environment Compare

UI Route: /projects/:slug/compare

Environment Compare provides a structured, side-by-side view of how configurations differ between any two environments within a project. It is the primary tool for pre-promotion verification — answering the question: "What will change when I promote from staging to production?"

Accessing the Compare View

Open the Project

Navigate to the project and select the Compare tab in the project navigation.

Select Source and Target Environments

Use the dropdown selectors to choose:

  • Source Environment: The environment you're comparing from (e.g., staging)
  • Target Environment: The environment you're comparing to (e.g., production)

Review the Diff

The compare view renders a diff grouped by tenant and service, showing all configuration differences between the two environments.

What is Compared

The compare view analyzes differences across multiple dimensions:

DimensionDescription
Helm ValuesPer-service Helm values differences between environments
Image TagsContainer image versions deployed in each environment
HQ VariablesEnvironment-scoped variable values
Kustomize OverlaysPer-tenant-environment overlay differences
Manifest OverlaysPer-tenant-environment raw manifest differences
Chart VersionsHelm chart versions if they differ between environments

Diff Format

The diff is presented in a familiar unified diff format:

# Tenant: acme-corp / Service: api
# Helm Values Diff

- replicaCount: 2
+ replicaCount: 3

- image:
-   tag: "v1.4.0"
+ image:
+   tag: "v1.3.8"

- resources:
-   limits:
-     memory: "512Mi"
+ resources:
+   limits:
+     memory: "1Gi"

Lines prefixed with - exist in the source environment but not the target. Lines prefixed with + exist in the target but not the source.

Filtering and Navigation

FilterDescription
By TenantShow diffs for a specific tenant only
By ServiceShow diffs for a specific workload only
By Diff TypeFilter to only image tag changes, value changes, or overlay changes
Changed OnlyHide sections with no differences (default: on)

Use Cases

ScenarioHow to Use Compare
Pre-promotion checkCompare stagingproduction before promoting a release
Debugging inconsistenciesCompare devstaging to find why a service behaves differently
Audit trailVerify that production matches staging after a promotion
Onboarding reviewCompare a new tenant's dev config against an established tenant

Environment Compare is read-only. To act on the differences you see, navigate to the values editor or create a release.


Bulk Image Update

UI Route: /projects/:slug/bulk-update

Bulk Image Update allows you to change container image tags across multiple tenants and environments in a single atomic operation. Instead of editing values for each tenant-environment-service combination individually, you select the scope and apply the update in one action.

Performing a Bulk Update

Open Bulk Update

Navigate to the project and select the Bulk Update tab.

Select the Workload

Choose which workload (service) you want to update. This filters the view to show only service deployments for that workload.

Select Target Scope

Use the tenant and environment checkboxes to select which combinations should receive the update.

SelectorBehavior
Select All TenantsAll tenants across selected environments
Select All EnvironmentsAll environments for selected tenants
Individual CheckboxesPrecise control over which tenant-environment pairs to update

Specify New Image Tag

Enter the new image tag (e.g., v1.5.0, sha-abc1234, latest).

Preview Changes

Before committing, GitOpsHQ shows a preview of all changes:

Changes to commit:
├── tenants/acme-corp/dev/api/values.yaml      image.tag: v1.4.0 → v1.5.0
├── tenants/acme-corp/staging/api/values.yaml  image.tag: v1.3.8 → v1.5.0
├── tenants/beta-inc/dev/api/values.yaml       image.tag: v1.4.0 → v1.5.0
└── tenants/beta-inc/staging/api/values.yaml   image.tag: v1.3.8 → v1.5.0

Total: 4 files changed

Commit

Click Apply to create a single Git commit with all image tag changes. The commit message includes the workload name, new tag, and affected scope.

Bulk updates bypass the release workflow by default — changes are committed directly to the hosted repository. If your project requires releases for all changes, coordinate bulk updates with your release process.


Image Update Webhook

Endpoint: POST /api/v1/webhooks/image-update

The image update webhook automates bulk image updates from CI/CD pipelines. When your CI pipeline builds and pushes a new container image, it can notify GitOpsHQ to update matching service deployments automatically.

Webhook Configuration

Configure the webhook in Project Settings → Webhooks → Image Update:

SettingDescription
Shared SecretHMAC secret for signature verification
Auto-Update PolicyWhich environments to auto-update (e.g., only dev and staging)
Image FilterRegex pattern to match image repositories (e.g., ghcr.io/myorg/.*)
Tag FilterRegex pattern to match accepted tag formats (e.g., ^v\d+\.\d+\.\d+$)

Request Format

{
  "image": "ghcr.io/myorg/api",
  "tag": "v1.5.0",
  "digest": "sha256:abc123...",
  "project": "my-project",
  "workload": "api",
  "environments": ["dev", "staging"],
  "tenants": ["*"],
  "timestamp": "2026-03-27T10:30:00Z"
}

Authentication

The webhook uses HMAC-SHA256 signature verification:

X-GitOpsHQ-Signature: sha256=<HMAC-SHA256(shared_secret, request_body)>

Replay Protection

GitOpsHQ prevents replay attacks by:

  1. Requiring a timestamp field in the payload
  2. Rejecting requests older than 5 minutes
  3. Storing processed request hashes in a shared store and rejecting duplicates

Response Codes

StatusMeaning
200 OKImage update accepted and committed
202 AcceptedImage update queued (async processing)
400 Bad RequestInvalid payload or missing required fields
401 UnauthorizedInvalid or missing signature
404 Not FoundProject or workload not found
409 ConflictDuplicate request (replay protection)
422 UnprocessableTag or image does not match configured filters

CI/CD Integration Examples

- name: Notify GitOpsHQ of new image
  run: |
    BODY='{"image":"ghcr.io/myorg/api","tag":"${{ github.sha }}","project":"my-project","workload":"api","environments":["dev"],"tenants":["*"],"timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}'
    SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "${{ secrets.GITOPSHQ_WEBHOOK_SECRET }}" | sed 's/.* //')
    curl -X POST https://app.gitopshq.io/api/v1/webhooks/image-update \
      -H "Content-Type: application/json" \
      -H "X-GitOpsHQ-Signature: sha256=$SIGNATURE" \
      -d "$BODY"
notify_gitopshq:
  stage: deploy
  script:
    - BODY='{"image":"registry.gitlab.com/myorg/api","tag":"'"$CI_COMMIT_SHORT_SHA"'","project":"my-project","workload":"api","environments":["dev"],"tenants":["*"],"timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}'
    - SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$GITOPSHQ_WEBHOOK_SECRET" | sed 's/.* //')
    - 'curl -X POST https://app.gitopshq.io/api/v1/webhooks/image-update -H "Content-Type: application/json" -H "X-GitOpsHQ-Signature: sha256=$SIGNATURE" -d "$BODY"'
stage('Notify GitOpsHQ') {
    steps {
        script {
            def body = """{"image":"docker.io/myorg/api","tag":"${env.BUILD_TAG}","project":"my-project","workload":"api","environments":["dev"],"tenants":["*"],"timestamp":"${new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone('UTC'))}"}"""
            def secret = credentials('gitopshq-webhook-secret')
            def signature = sh(script: "echo -n '${body}' | openssl dgst -sha256 -hmac '${secret}' | sed 's/.* //'", returnStdout: true).trim()
            httpRequest(
                url: 'https://app.gitopshq.io/api/v1/webhooks/image-update',
                httpMode: 'POST',
                contentType: 'APPLICATION_JSON',
                customHeaders: [[name: 'X-GitOpsHQ-Signature', value: "sha256=${signature}"]],
                requestBody: body
            )
        }
    }
}

ArgoCD Webhook

Endpoint: POST /api/v1/webhooks/argocd

The ArgoCD webhook receives sync status events from ArgoCD notification triggers. This provides a secondary channel for status updates alongside the agent's gRPC reporting.

Configuration

  1. In ArgoCD, configure a notification trigger that sends HTTP webhooks on sync events
  2. Point the webhook URL to your GitOpsHQ instance's ArgoCD webhook endpoint
  3. Configure the HMAC shared secret in both ArgoCD and GitOpsHQ

Payload Format

{
  "app": {
    "metadata": {
      "name": "my-project-acme-corp-dev-api",
      "namespace": "argocd"
    },
    "status": {
      "sync": {
        "status": "Synced",
        "revision": "abc123def456"
      },
      "health": {
        "status": "Healthy"
      }
    }
  },
  "timestamp": "2026-03-27T10:35:00Z"
}

GitOpsHQ parses the application name to identify the project, tenant, environment, and service, then updates the corresponding deployment status.


Best Practices

PracticeRecommendation
Always compare before promotingUse Environment Compare to verify expected changes before running a promotion
Use webhooks for CI-triggered updatesAutomate image tag updates rather than manually running bulk updates
Limit auto-update environmentsConfigure webhooks to auto-update only dev (and optionally staging). Require manual promotion to production.
Combine with approval policiesUse approval policies on production environments so webhook-triggered changes still require human review for production deployment
Monitor webhook deliveriesCheck webhook delivery logs for failed or rejected requests. A high failure rate may indicate misconfigured secrets or payload issues.
Use tag filtersConfigure tag filters on webhook endpoints to reject non-semver or non-compliant tags from being deployed

On this page