Skip to content

GauJosh/aca-operator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

23 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SynosCD - GitOps Operator for Azure Container Apps

Build, Test, Scan & Tag License: MIT Python 3.9+ Code style: black

Declarative, GitOps-based application management for Azure Container Apps. Deploy ACA apps from Git with automatic drift detection, health-aware reconciliation, and safety guardrails.

SynosCD brings Flux-like GitOps to Azure Container Apps:

  • βœ… Git as Source of Truth - YAML manifests define desired state
  • βœ… Automatic Drift Healing - Operator continuously reconciles desired↔live state
  • βœ… Health-Aware Reconciliation - Failed/unready apps trigger automatic re-apply
  • βœ… Safe by Default - Managed ownership tagging + protected app list prevent accidental deletion
  • βœ… Local Development - Full support for az login based testing
  • βœ… Production Grade - Structured JSON logging, ARM validation, LRO polling

What SynosCD Supports

  • Pull-based reconciliation loop (configurable interval)
  • GitHub App authentication for config repo access
  • Real ARM create/update/delete/list/get calls for ACA resources
  • Health-aware reconciliation (re-apply if app is not healthy)
  • Managed ownership tagging on apps (synoscd-managed=true)
  • Optional prune mode for managed apps missing from Git
  • Protected app list to avoid deleting critical apps (e.g. synoscd-operator)

Quick Start

For most users, the fastest path is:

  1. Download a release artifact from GitHub Releases
  2. Set the required environment variables
  3. Run a read-only command such as synos get apps

If you are running from a repo checkout or source install and your GitHub App secrets are already stored in Azure Key Vault, you can also use the bootstrap helper:

python scripts/bootstrap_env.py --run synos get apps

That command loads the required environment variables and runs the CLI without needing source or eval.

Install / Download

SynosCD releases are published in GitHub Releases.

Each tagged release includes:

  • synoscd-<version>.tar.gz source distribution
  • synoscd-<version>-py3-none-any.whl wheel
  • synos-linux-x86_64.tar.gz
  • synos-macos-x86_64.tar.gz
  • synos.exe

Choose the format that matches how you want to run it:

Option 1: Install from wheel or source package

Download the release artifact you want, then install it with pip:

pip install synoscd-<version>-py3-none-any.whl
# or
pip install synoscd-<version>.tar.gz

Option 2: Run the standalone binary

  • Windows: use synos.exe
  • Linux/macOS: extract the tarball and run synos

Example on Linux/macOS:

tar -xzf synos-linux-x86_64.tar.gz
./synos --help

Example on Windows PowerShell:

.\synos.exe --help

Configuration

SynosCD is configured entirely through environment variables.

Required

  • SYNOSCD_GITHUB_APP_ID
  • SYNOSCD_GITHUB_APP_PRIVATE_KEY
  • SYNOSCD_GITHUB_APP_INSTALLATION_ID
  • SYNOSCD_GITHUB_REPO_OWNER
  • SYNOSCD_GITHUB_REPO_NAME
  • SYNOSCD_AZURE_SUBSCRIPTION_ID
  • SYNOSCD_AZURE_RESOURCE_GROUP
  • SYNOSCD_AZURE_CONTAINER_APP_ENVIRONMENT

Common Optional

  • SYNOSCD_GITHUB_CONFIG_PATH (default: apps)
  • SYNOSCD_RECONCILE_INTERVAL_SECONDS (default: 300)
  • SYNOSCD_AZURE_MANAGED_IDENTITY_CLIENT_ID (needed for user-assigned MI in ACA)
  • SYNOSCD_PRUNE_ENABLED (default: false)
  • SYNOSCD_PROTECTED_APPS_CSV (default: synoscd-operator)

Authenticate to Azure

If you are running locally, authenticate first:

az login
az account set --subscription <your-subscription-id>

Set environment variables and run

Bash / Git Bash / Linux / macOS

export SYNOSCD_GITHUB_APP_ID=<app-id>
export SYNOSCD_GITHUB_APP_PRIVATE_KEY="$(cat private-key.pem)"
export SYNOSCD_GITHUB_APP_INSTALLATION_ID=<installation-id>
export SYNOSCD_GITHUB_REPO_OWNER=<owner>
export SYNOSCD_GITHUB_REPO_NAME=<repo>
export SYNOSCD_AZURE_SUBSCRIPTION_ID=<sub-id>
export SYNOSCD_AZURE_RESOURCE_GROUP=<resource-group>
export SYNOSCD_AZURE_CONTAINER_APP_ENVIRONMENT=<ace-name>

synos get apps

Windows PowerShell

$env:SYNOSCD_GITHUB_APP_ID = '<app-id>'
$env:SYNOSCD_GITHUB_APP_PRIVATE_KEY = Get-Content .\private-key.pem -Raw
$env:SYNOSCD_GITHUB_APP_INSTALLATION_ID = '<installation-id>'
$env:SYNOSCD_GITHUB_REPO_OWNER = '<owner>'
$env:SYNOSCD_GITHUB_REPO_NAME = '<repo>'
$env:SYNOSCD_AZURE_SUBSCRIPTION_ID = '<sub-id>'
$env:SYNOSCD_AZURE_RESOURCE_GROUP = '<resource-group>'
$env:SYNOSCD_AZURE_CONTAINER_APP_ENVIRONMENT = '<ace-name>'

synos get apps

One-command bootstrap from Key Vault

If you are running from a repo checkout or source install and your GitHub App secrets are in Key Vault (for example synoscd-vault), you can auto-load everything with one command:

python scripts/bootstrap_env.py --run synos get apps

Other common examples:

python scripts/bootstrap_env.py --run synos config
python scripts/bootstrap_env.py --run synos get source
python scripts/bootstrap_env.py --run synos get apps

You can also generate shell exports if needed:

python scripts/bootstrap_env.py --print-exports

This script auto-detects subscription, resource group, ACE, and loads GitHub App secrets from AKV:

  • github-app-id
  • github-app-installation-id
  • github-app-private-key

Optional overrides:

python scripts/bootstrap_env.py \
  --vault-name synoscd-vault \
  --rg-hint synoscd-dev \
  --ace-hint <ace-name> \
  --config-repo-url https://github.com/<owner>/<repo>.git \
  --run synos get source

Convenience wrappers:

# Git Bash / Linux / macOS
./scripts/synos-env.sh get apps

# PowerShell
./scripts/synos-env.ps1 get apps

Common CLI Commands

Read and inspect

  • synos config β€” show active SynosCD configuration
  • synos get apps [-o table|json|yaml] β€” list desired Apps with live health context
  • synos get source [-o table|json|yaml] β€” show Git source details and latest commit
  • synos get status [-o table|json|yaml] β€” show reconciliation summary
  • synos status app <name> [-o table|json|yaml] [--watch] β€” show detailed live status for one app
  • synos status all [-o table|json|yaml] [--watch] β€” show detailed live status for all apps
  • synos describe <name> [-o table|json|yaml] [--watch] β€” alias for synos status app <name>
  • synos diff [--app <name>] [-o table|json|yaml] β€” show desired vs live state differences

Reconcile and operate

  • synos reconcile source [--watch] β€” reconcile from Git source now
  • synos reconcile app <name> β€” reconcile one app now
  • synos sync [--dry-run] β€” run one reconciliation pass now
  • synos operator [--interval <seconds>] β€” run the continuous operator loop

Logs

  • synos logs app <name> [--tail N] [--follow] β€” show or stream logs for one ACA app
  • synos logs operator [--tail N] [--follow] β€” show or stream logs for the SynosCD operator

Runtime control

  • synos set --show β€” show current operator runtime settings
  • synos set --interval <seconds> [--yes] β€” change reconcile interval on the running operator
  • synos set --prune/--no-prune [--yes] β€” enable or disable prune on the running operator
  • synos set --protected-apps <csv> [--yes] β€” update protected apps on the running operator
  • synos set --max-concurrent <n> [--yes] β€” update max concurrent reconciles on the running operator
  • synos suspend <name> [--yes] β€” suspend reconciliation for one app at runtime
  • synos resume <name> [--yes] β€” resume reconciliation for one app at runtime

Setup and admin

  • synos bootstrap ... β€” validate/setup bootstrap values for operator configuration

Global flags

  • --verbose or -v β€” increase log verbosity
  • --debug β€” enable debug logging

Daily Usage Flow

A typical user flow looks like this:

  1. Confirm configuration with synos config
  2. Check source connectivity with synos get source
  3. List managed apps with synos get apps
  4. Inspect one app with synos status app <name> or synos describe <name>
  5. Force a refresh with synos reconcile app <name> or synos reconcile source

If you need temporary operational control without changing Git:

  • use synos suspend <name> to pause reconciliation for one app
  • use synos resume <name> to re-enable reconciliation
  • use synos set --show to inspect live operator settings
  • use synos set --interval ... or related flags to change runtime operator behavior

Architecture Model

  • One operator per ACE/environment
  • One ops repo as source of truth (recommended)
  • Apps defined as YAML under apps/ (or custom config path)
  • Terraform/Bicep owns platform foundation (ACE, identity, network, etc.)
  • SynosCD owns app-level reconciliation

App Manifest (Git Contract)

apiVersion: synoscd.io/v1alpha1
kind: App
metadata:
  name: demo-app
  labels:
    synoscd.io/managed: "true"
spec:
  containers:
    - name: app
      image: mcr.microsoft.com/azuredocs/containerapps-helloworld:latest
      cpu: 0.25
      memory: 0.5Gi
  ingress:
    enabled: true
    external: true
    targetPort: 80
  scale:
    minReplicas: 1
    maxReplicas: 3
  suspend: false

Run from Source (Development)

Local run is supported via DefaultAzureCredential.

1) Setup

python -m venv venv
source venv/bin/activate  # Windows Git Bash: source venv/Scripts/activate
pip install -e .

You can run SynosCD in any of these ways:

synos --help
python -m synoscd --help
python src/synoscd/cli.py --help

The package is configured for standard installation on Windows, Linux, and macOS.

Operator Deployment (Maintainers / Platform Admins)

This section is mainly for maintainers or platform admins who build and deploy the SynosCD operator image.

ACR_NAME="synoscdacr"
IMAGE="synoscdacr.azurecr.io/synoscd:v0.2.6"

podman build -t "$IMAGE" .
TOKEN=$(az acr login -n "$ACR_NAME" --expose-token --query accessToken -o tsv)
podman login synoscdacr.azurecr.io -u 00000000-0000-0000-0000-000000000000 -p "$TOKEN"
podman push "$IMAGE"

MI_CLIENT_ID=$(az identity show -g synoscd-dev -n synoscd-identity --query clientId -o tsv)

az containerapp update -n synoscd-operator -g synoscd-dev \
  --image "$IMAGE" \
  --set-env-vars \
    SYNOSCD_AZURE_MANAGED_IDENTITY_CLIENT_ID="$MI_CLIENT_ID" \
    SYNOSCD_RECONCILE_INTERVAL_SECONDS=30 \
    SYNOSCD_PRUNE_ENABLED=false \
    SYNOSCD_PROTECTED_APPS_CSV="synoscd-operator"

How Reconciliation Works

Each cycle:

  1. Fetch YAML resources from Git (apps/ by default)
  2. Parse into SynosCD resources (App, Project)
  3. List live ACA apps from Azure
  4. For each managed App:
    • If missing or drifted, apply desired state via ARM
    • If live app health is failed/not ready, force reconcile
  5. Optionally prune managed apps missing from Git (if enabled)

Drift Heal, Safety, and Prune

  • Drift heal: Enabled by default via desired-vs-live comparison
  • Health-aware reconcile: Failed/unready app is re-applied even when spec is unchanged
  • Ownership tag: SynosCD writes synoscd-managed=true on managed apps
  • Prune mode: Disabled by default (SYNOSCD_PRUNE_ENABLED=false)
  • Protected apps: Never pruned if listed in SYNOSCD_PROTECTED_APPS_CSV

Prune behavior (when enabled)

Prune deletes only apps that are:

  • tagged as managed by SynosCD, and
  • not present in desired Git state, and
  • not in protected app list

Release / Distribution Strategy

SynosCD publishes multiple distribution formats from GitHub Releases:

Format Best for
Source tarball (.tar.gz) Contributors and source installs
Wheel (.whl) Standard Python installs and pip mirrors
Standalone binary (synos / synos.exe) Direct download and run with env vars already configured

Recommended usage:

  1. Most users: download a GitHub Release artifact
  2. Python environments: install the wheel or source distribution with pip
  3. No-Python client machines: download the standalone release binary

If you use a standalone binary, you still need to set the same SynosCD env vars before running the CLI.

Flux-style mapping

  • flux reconcile source git β†’ synos reconcile source
  • flux reconcile kustomization <name> β†’ synos reconcile app <name>
  • flux get source git β†’ synos get source
  • flux get ks / flux get hr β†’ synos get apps (App-centric model)

Resource model

SynosCD is intentionally simpler than Flux.

  • Flux uses separate resource types such as GitRepository, Kustomization, and HelmRelease
  • SynosCD uses a single app-centric model
  • Git connectivity is configured on the operator itself
  • The operator pulls manifests directly and reconciles them to Azure Container Apps

Today, the primary Git contract is the App manifest. You do not need separate Source or Kustomization manifests unless SynosCD later grows into a multi-source or multi-tenant platform.

Troubleshooting

Operator revision fails with ErrImagePull

Image tag not found in ACR. Verify tags:

az acr repository show-tags -n synoscdacr --repository synos -o table

Azure tag validation failure (InvalidTagNameCharacters)

Use ARM-safe tags (SynosCD now uses synoscd-managed=true).

App keeps failing after successful PUT 201

Check app resource provisioning + revision state:

az containerapp show -n <app> -g <rg> --query "{provisioning:properties.provisioningState,latest:properties.latestRevisionName,ready:properties.latestReadyRevisionName}" -o jsonc
az containerapp revision list -n <app> -g <rg> -o table

If Azure CLI log streaming has extension issues, use activity log:

APP_ID=$(az containerapp show -n <app> -g <rg> --query id -o tsv)
az monitor activity-log list --resource-id "$APP_ID" --status Failed --offset 2h -o jsonc

CPU/memory validation fails

ACA requires valid CPU/memory combinations (for example 0.25 + 0.5Gi, 0.5 + 1Gi).

Repository Layout

src/synoscd/
  __init__.py
  schema.py
  config.py
  logger.py
  github.py
  aca.py
  reconciler.py
  cli.py

examples/
docs/

Current Maturity

  • Core GitOps loop: implemented
  • ARM app create/update/list/get/delete: implemented
  • Drift heal + health-aware retry: implemented
  • Safe ownership tagging + optional prune: implemented
  • Multi-repo orchestration: not implemented (single repo per operator recommended)

About

GitOps Operator for Azure Container Apps - Bring Flux-like declarative GitOps to Azure. SynosCD automatically syncs ACA apps from Git manifests with drift detection, health-aware reconciliation, and safe pruning. Built for operators who want app-level declarative management without the complexity of full fleet orchestration.

Topics

Resources

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors