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
| File | Lives in | Written by |
|---|---|---|
environment-profile.yaml | Platform repo root | You |
Reusable CI workflow, .github/workflows/ci-<service>.yaml | Platform repo | TruStacks, through pull requests you merge |
Helm chart or Kustomize base and overlays, under gitops/ | Platform repo | TruStacks, through pull requests |
ArgoCD Applications, under argo-apps/ | Platform repo | TruStacks, through pull requests |
Caller workflow, .github/workflows/ci.yaml | Service repo | You, once per service |
Root ArgoCD Application watching argo-apps/ | Your cluster | You, 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 hereKustomize 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: writePin 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 accountWhy 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: truekubectl apply -f trustacks-platform-root.yamlAutomated 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
- 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.
- 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.
- You review and merge the pull request in the platform repo.
- The root Application picks up any new child Application.
- 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=argocdIt 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
- Platform repo layout reference
- Proposal lifecycle · every stage a proposal passes through
- Credentials and trust boundary
- Quickstart · the whole flow, end to end