Skip to main content

Push-Based GitOps for Staging (Test) Environments

Metadata
  • ID: RA-001-GITOPS-LITE
  • DOI: DOI
  • Status: Stable / Production-Proven
  • Author: Ivan Baha (ORCID: 0009-0005-7024-7724)
  • Published Date: 2026-02-17
  • Last Updated: 2026-02-17
  • Tags: #devops #docker #gitops #cost-optimization #ci-cd

1. Executive Summary​

This architecture defines a lightweight, cost-effective Continuous Deployment (CD) pipeline designed for Dev/Test/Staging environments in small-to-medium projects.

It achieves the reliability of GitOps (versioned infrastructure state, automated reconciliation) without the complexity or cost overhead of managed Kubernetes clusters. It utilizes GitHub Actions, Docker Compose, and a single Virtual Machine (VM).

2. Applicability Criteria​

Use this pattern when​

  • Cost Sensitivity: The project budget cannot justify managed control planes (EKS/GKE) (~$70-100/mo, in addition to the main resources cost).
  • Team Size: The engineering team is small (1-5 devs) and lacks dedicated DevOps specialists.
  • Scale: The workload fits within a vertically scaled VM (e.g., CPU and RAM can be increased to meet the requirements) or can be split across a few distinct VMs.
  • Downtime Tolerance: The environment can tolerate brief (<5s) downtime during deployment (acceptable for Dev/Test/Staging).

Do NOT use this pattern when​

  • Zero-Downtime is Critical: Mission-critical production systems require rolling updates with no connection drops.
  • Complex Orchestration: You need advanced scheduling, auto-scaling based on CPU/Memory, or service meshes.

3. Architecture Overview​

The "GitOps-Lite" Pattern​

The core principle is the decoupling of Application Code from Infrastructure State.

  • Application Repositories build artifacts (Docker Images).
  • The Infrastructure Repository defines the desired state (docker-compose.yml).
  • The Handshake: A CI workflow updates the Infrastructure Repo, which triggers the actual deployment.

4. Implementation Details​

4.1. The Three-Stage Pipeline​

Stage 1: Build & Publish (Source Repo)​

  • Trigger: Push (merge) to main.
  • Action: Executes automated quality gates and tests, builds a Docker image tagged with semantic version (e.g., v1.2.0), and pushes to the container registry.
  • Output: Triggers the Update Infrastructure workflow.

Stage 2: Update State (Infrastructure Repo)​

This step updates the source of truth. It uses default Linux tools, such as sed or yq, to modify the deployment manifest.

# .github/workflows/update-manifest.yml
name: Update Deployment State
on: [repository_dispatch]

jobs:
update-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Update Docker Compose
run: |
sed -i "s|image: .*/${{ github.event.client_payload.service }}:.*|image: registry.com/${{ github.event.client_payload.service }}:${{ github.event.client_payload.version }}|" docker-compose.yml
- name: Commit Change
run: |
git commit -am "chore(deploy): update ${{ github.event.client_payload.service }} to ${{ github.event.client_payload.version }}"
git push

Stage 3: Surgical Deployment (The VM)​

The push to the Infrastructure Repo triggers the final deployment via SSH. Crucially, it uses Smart Routing based on the commit message to restart only the affected service.

#!/bin/bash
# deploy.sh
COMMIT_MSG=$(git log -1 --pretty=%B)

if [[ "$COMMIT_MSG" =~ "service-a" ]]; then
echo "Deploying Service A..."
docker compose pull service-a
docker compose up -d --force-recreate service-a
elif [[ "$COMMIT_MSG" =~ "service-b" ]]; then
echo "Deploying Service B..."
docker compose pull service-b
docker compose up -d --force-recreate service-b
else
echo "Full Redeployment (Fallback)"
docker compose pull
docker compose up -d --remove-orphans
fi

4.2. Secrets Management​

  • Method: .env files stored on the VM filesystem, with a naming convention to distinguish configs by service (e.g. .<service-name>.env).
  • Justification: For Test environments, this offers the highest security-to-complexity ratio.
  • Evolution: Can be upgraded to inject secrets via GitHub Actions or fetch from HashiCorp Vault/Bitwarden at runtime.

5. Scalability & Evolution Path​

While tailored for a single node, this architecture supports horizontal growth:

High Availability (HA)​

  1. Deploy a second identical VM.
  2. Place a Load Balancer (LB) in front of them.
  3. Update the GitHub Action to deploy to VM1 → Health Check → VM2.
  4. Result: Zero-downtime deployments.

Service Splitting​

  • Resource-intensive services (e.g., data-processor) can be moved to dedicated VMs.
  • The Infrastructure Repository remains the single source of truth, but the deploy script routes SSH commands to the appropriate server based on the service name.

6. Trade-off Analysis​

FeatureBenefitDrawback
CostExtremely Low (Free Tier + cheap VM)N/A
ComplexityLow (Standard Docker/Bash)Manual setup of VM is required initially
ObservabilityGit History acts as an Audit LogNo built-in dashboard (unlike ArgoCD)
ReliabilityAtomic updates via GitSingle Point of Failure (if using 1 VM)

7. Conclusion​

This architecture provides a pragmatic "GitOps-Lite" foundation. It allows engineering teams to focus on product velocity in the early stages while maintaining strict infrastructure discipline. It effectively bridges the gap between manual deployment scripts and enterprise-grade Kubernetes.