> Markdown version of https://archtenet.dev/docs/reference-architectures/ra-001-gitops-lite — the same page without the site chrome.
> Index of everything published here: https://archtenet.dev/llms.txt

# RA-001 — Push-Based GitOps for Staging (Test) Environments

> A lightweight CI/CD pattern using Docker Compose and GitHub Actions.

- **HTML version:** https://archtenet.dev/docs/reference-architectures/ra-001-gitops-lite

:::info[Metadata]

- **ID:** `RA-001-GITOPS-LITE`
- **DOI:** [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18750752.svg)](https://doi.org/10.5281/zenodo.18750752)
- **Status:** Stable / Production-Proven
- **Author:** Ivan Baha (ORCID: [0009-0005-7024-7724](https://orcid.org/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 (&lt;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.

```mermaid
graph TD
    subgraph CI_CD [GitHub Ecosystem]
        style CI_CD fill:#f9f9f9,stroke:#333,stroke-width:1px
        AppRepo[<b>App Repository</b><br/>Source Code]
        InfraRepo[<b>Infra Repository</b><br/>State: docker-compose.yml]
        Registry[(<b>Container Registry</b><br/>Images)]
    end

    subgraph Runtime [Test Environment - VM]
        style Runtime fill:#e1f5fe,stroke:#0277bd,stroke-width:1px
        VM[<b>Docker Compose</b><br/>Reverse Proxy + Services]
    end

    AppRepo -->|1. Build & Push| Registry
    AppRepo -- "2. Update Tag (The Handshake)" --> InfraRepo
    InfraRepo -->|3. Trigger Deployment via SSH| VM
    VM -.->|4. Pull & Restart| Registry
```

## 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.

```yaml
# .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.

```bash
#!/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

| Feature           | Benefit                              | Drawback                                 |
| ----------------- | ------------------------------------ | ---------------------------------------- |
| **Cost**          | Extremely Low (Free Tier + cheap VM) | N/A                                      |
| **Complexity**    | Low (Standard Docker/Bash)           | Manual setup of VM is required initially |
| **Observability** | Git History acts as an Audit Log     | No built-in dashboard (unlike ArgoCD)    |
| **Reliability**   | Atomic updates via Git               | Single 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.