Skip to Content
InstallationGitOps integration

GitOps integration

TruStacks writes delivery config into your platform repo as pull requests. Your CI runs the pipeline, and your ArgoCD deploys what you merge. TruStacks does not install, configure, or operate either one. This page covers what lands where in the platform repo, how the CI workflow is wired to your service repo, and how to point ArgoCD at the result.

Who owns what

FileLives inWritten by
environment-profile.yamlPlatform repo rootYou
Reusable CI workflow, .github/workflows/ci-<service>.yamlPlatform repoTruStacks, through pull requests you merge
Helm chart or Kustomize base and overlays, under gitops/Platform repoTruStacks, through pull requests
ArgoCD Applications, under argo-apps/Platform repoTruStacks, through pull requests
Caller workflow, .github/workflows/ci.yamlService repoYou, once per service
Root ArgoCD Application watching argo-apps/Your clusterYou, once

TruStacks never writes to your service repos. The constitution also rejects any proposal that writes outside gitops/, argo-apps/, or .github/workflows/ in the platform repo.

Platform repo layout

With the default Helm renderer:

platform-repo/ ├── environment-profile.yaml # yours: what you run ├── .github/workflows/ │ └── ci-<service>.yaml # reusable workflow (on: workflow_call) ├── gitops/ │ └── <application>/ │ └── <cluster>/ │ └── <service>/ # one chart per service per cluster │ ├── Chart.yaml │ ├── values.yaml # image.tag lives here │ └── templates/ └── argo-apps/ └── argo-apps-<cluster>/ └── <service>-application.yaml # points at gitops/<application>/<cluster>/<service>

With the Kustomize renderer (Beta), each service gets one base and one overlay per cluster, and ArgoCD syncs the overlay:

gitops/ └── <application>/ └── <service>/ ├── base/ └── overlays/ └── <cluster>/ # the image tag is pinned here
⚠️

Kustomize is Beta. It is generated and validated with kustomize build before every pull request, but it has had far less real-world use than Helm.

<application>, <cluster>, and <service> come from your Application’s name, its target clusters, and the service name in TruStacks. You can change the service root per Application (for example to gitops/apps/{service}) on the Application’s renderer card; argo-apps/argo-apps-<cluster>/ is fixed.

The CI workflow: reusable, plus a caller

The pipeline has to test and build your service code, but TruStacks can only write to the platform repo. So the workflow it writes is a reusable workflow (on: workflow_call, with no other triggers). Your service repo calls it, and because a called workflow runs in the caller’s context, it checks out and builds the service.

Add this to each service repo, once:

# .github/workflows/ci.yaml in the SERVICE repo name: ci on: push: branches: [main] # your default branch pull_request: jobs: trustacks: # The called workflow pushes an image. Pull requests from forks get a # read-only token and cannot push, so skip them instead of failing. if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository uses: <owner>/<platform-repo>/.github/workflows/ci-<service>.yaml@<commit-sha> permissions: contents: read packages: write

Pin the called workflow to a commit SHA in the platform repo, for example the merge commit of the TruStacks pull request that added it. It runs with packages: write, so a pin means nothing reaches your service’s CI until you move it; when you merge a TruStacks change to this workflow, move the pin in the same review. @main also works and follows every merge automatically, at the cost of trusting whoever can push to the platform repo’s main. The proposal summary shows the snippet with @main and on: [push, pull_request]; the pinned form above is the hardened one.

Same-repo pull requests still build and push an image, tagged with the pull request’s commit SHA. Image tag promotion follows only your default branch, so those images are never proposed for deployment.

The workflow runs your tests and linter, builds the image from the Dockerfile in the service repo, and pushes it to GitHub Container Registry tagged with the commit SHA. The tools it runs come from your Environment Profile; a tool you have not declared gets no step.

Private platform repo: allow other repos to use its workflows, once, or the caller fails with workflow was not found:

PLATFORM_REPO=your-org/your-platform-repo # owner/name of your platform repo gh api --method PUT "repos/$PLATFORM_REPO/actions/permissions/access" \ -f access_level=organization # or access_level=user for a personal account

Why no trigger in the platform repo: a workflow that ran on its own there would build the GitOps repo, which has no service code, and a push trigger there would let changes bypass the policy gate.

The root Application

Apply one Application by hand that watches argo-apps/ recursively. It is not committed to the platform repo; everything it watches is.

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: trustacks-platform-root namespace: argocd spec: project: default source: repoURL: https://github.com/<owner>/<platform-repo> targetRevision: main path: argo-apps directory: recurse: true destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: true
kubectl apply -f trustacks-platform-root.yaml

Automated sync on the root only creates and removes the child Application objects as pull requests merge. It does not deploy workloads; see below. Whether the root syncs automatically is your choice.

ArgoCD needs read access to the platform repo. For a private repo, add it to ArgoCD with a deploy key or token the way you add any private repo. These credentials are ArgoCD’s own; the runner never sees them.

Sync is manual

Every ArgoCD Application TruStacks writes uses manual sync, with CreateNamespace=true so the first sync can create the target namespace. Merging a pull request updates Git; it does not change what is running until someone syncs.

For a target cluster with tier prod this is policy, not just a default: the constitution denies a proposal that sets automated sync on that cluster’s Application, whatever the cluster is named. Runners older than 0.2.14-rc25 do not send the tier, so for them the check falls back to a cluster literally named prod (argo-apps/argo-apps-prod/). Enabling automated sync anywhere else is your decision, made in your own reviewed change.

This page is about Kubernetes targets. An ECS Fargate target (Beta) has no ArgoCD: merging deploys through a workflow in the platform repo. See ECS Fargate layout.

How a change reaches the cluster

  1. Promote to proposal. The DevOps Engineer reads your platform repo first and keeps anything you have customized, noting it in the pull request as preservation notes.
  2. Validation and policy. The chart is rendered and the workflow parsed, then the proposal is evaluated against the constitution. A deny or a failed check stops it; no pull request opens.
  3. You review and merge the pull request in the platform repo.
  4. The root Application picks up any new child Application.
  5. Someone syncs the child Application, and ArgoCD deploys.

Image tag promotion

The tag in values.yaml (or the Kustomize overlay) only changes through a pull request.

When your service repo’s default branch moves, the hosted control plane notices and your runner opens a proposal against the platform repo that sets the new commit SHA as the image tag. It goes through the same validation, policy gate, and human review as any proposal.

  • CI never writes to the platform repo. The build’s token is scoped to the service repo, and a build that repointed production would skip policy and review.
  • “Deployed is behind newest” is normal. It means a tag bump is waiting for review.
  • A quick approval can land before the image exists. The pod retries the pull and starts once CI pushes the image.

Deploy status (optional)

Turn on the observer on an installed runner (NAMESPACE and VERSION as in Runner install):

helm upgrade trustacks-runner oci://ghcr.io/trustacks/charts/runner \ --version "$VERSION" -n "$NAMESPACE" --reset-then-reuse-values \ --set observer.enabled=true --set observer.argocdNamespace=argocd

It then watches ArgoCD Applications in that namespace, read-only (get, list, watch; it cannot sync, roll back, or delete), and reports sync and health status back. Applications in the app then show deploy status. The control plane separately checks whether proposal pull requests have merged.

⚠️

Beta. Deploy status is new. It only reports what ArgoCD says and never changes anything, so if the chip stays empty, check that the observer is enabled and that argocdNamespace is the namespace your ArgoCD Application resources live in.

Other GitOps controllers

Proposals for a Kubernetes target always include an ArgoCD Application; the constitution requires one (proposal.has_argocd_application). Flux and other controllers are not supported today. An ECS Fargate target (Beta) has no GitOps controller: its deploy workflow applies the merged task definition.

Where to go next

Last updated on