GitOps Deployments with Argo CD and GitHub Actions
An Argo CD release splits in two: the GitHub Actions workflow builds by digest and commits the manifest, and the in-cluster controller applies it.
Last verified:
An Argo CD release splits a deployment across two systems: the GitHub Actions workflow builds the image, resolves its digest, and commits that digest into a manifest in a config repository, and the Argo CD controller running inside the cluster picks the commit up and applies it. The workflow therefore holds a registry credential and a git write token, while a kubeconfig never enters the run, which is what takes cluster credentials out of GitHub Actions entirely.
This page gives the responsibility split job by job, a workflow file that builds by digest and commits the manifest change with no cluster credentials present, the verification job that waits for the rollout and reports its result back to the run, runner sizes and per-minute rates for each of the three jobs, and the failure modes that appear once every merge goes through this path.
Overview
Argo CD follows the GitOps model, where the desired configuration is pushed to git first and the cluster state then syncs to what git says (automation from CI pipelines). The consequence for GitHub Actions is a hard boundary. With an automated sync policy configured, pipelines no longer need direct access to the Argo CD API server to perform the deployment, and the pipeline instead makes a commit and push to the repository that tracks the manifests (automated sync policy).
Here is where each piece of work lands and which credential it needs.
| Step | Runs in | Credential it holds |
|---|---|---|
| Build the image and push it to the registry | GitHub Actions job | Short lived registry credential from OpenID Connect |
| Resolve the immutable digest of what was pushed | GitHub Actions job | None |
| Rewrite the image reference in the manifest and commit it | GitHub Actions job | Git write token scoped to the config repository |
| Detect the new commit | Argo CD repo server in the cluster | Argo CD's own repository credential |
| Apply the manifests and reconcile drift | Argo CD application controller | In-cluster service account |
| Report synced and healthy | Argo CD API server | Read scoped project token, used only by the verify job |
Two facts about that middle row decide how the pipeline feels. Argo CD polls repositories every three minutes to detect changes to the manifests, and the API server can be configured to receive webhook events from the git provider to remove that delay (webhook configuration). Argo CD's own guidance is to keep the Kubernetes manifests in a repository separate from the application source (best practices), which means the commit step usually writes to a second repository and therefore needs a credential that GITHUB_TOKEN cannot provide.
The GitHub Actions side of this is three Linux jobs. All three jobs here belong on Linux; the label catalog is on the cloud runners page. The pipeline that applies manifests directly with kubectl or helm instead, and the private cluster reach it needs, is covered on Kubernetes deploy jobs on GitHub Actions.
Configuration
The workflow below has a build job and a promote job. The build job produces a digest. The promote job checks out the config repository, rewrites the image reference, and pushes. Nothing in either job can talk to the cluster.
name: release-api
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
outputs:
image: ${{ steps.ecr.outputs.registry }}/api@${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111122223333:role/github-actions-build
aws-region: us-east-1
- uses: aws-actions/amazon-ecr-login@v2
id: ecr
- name: Build and push
id: push
uses: Warpbuilds/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.ecr.outputs.registry }}/api:${{ github.sha }}
profile-name: "api-images-x64"
promote:
needs: build
runs-on: warp-ubuntu-latest-x64-2x
steps:
- uses: actions/checkout@v4
with:
repository: acme/cluster-config
ref: main
ssh-key: ${{ secrets.CONFIG_REPO_DEPLOY_KEY }}
persist-credentials: true
- name: Point the overlay at the new digest
working-directory: overlays/production
run: kustomize edit set image acme/api=${{ needs.build.outputs.image }}
- name: Commit and push
run: |
git config user.name "release-bot"
git config user.email "[email protected]"
git commit -am "api: ${{ needs.build.outputs.image }}"
for attempt in 1 2 3; do
git pull --rebase && git push && exit 0
sleep 5
done
exit 1Four details in that file carry the design.
The promote job consumes a digest through a job output, so the manifest records the exact bytes that were built and a retag upstream cannot change what the cluster runs. What an image digest identifies covers why that reference is stable, and build once and deploy the same artifact everywhere covers the promotion pattern this enables across environments.
kustomize edit set image is the manifest edit Argo CD's own pipeline documentation uses, alongside a kubectl patch --local form for plain YAML (automation from CI pipelines). Either one keeps the commit to a single line of change, which keeps the diff reviewable and the revert trivial.
The push is retried on a rebase. Every service in the fleet writes to the same config repository, so two releases finishing seconds apart will collide on a non fast-forward push, and the loop above is the cheapest fix.
The credential is a deploy key scoped to the config repository alone. The workflow-level permissions block stays at contents: read, and no step receives a token that can write to the source repository (automatic token authentication). Each WarpBuild runner is a virtual machine created for one job and destroyed when the job ends, with its own encrypted storage volume, and WarpBuild does not access or store build secrets (security documentation), so the deploy key lives as long as the promote job does.
Waiting for the rollout and reporting back
A push that nobody watches is a deployment nobody has confirmed. The verify job below downloads the argocd CLI from the API server, which keeps the client version aligned with the server, and blocks until the application is both synced and healthy (argocd app wait).
verify:
needs: [build, promote]
runs-on: warp-ubuntu-latest-x64-2x
env:
ARGOCD_SERVER: argocd.internal.acme.example
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_PROJECT_TOKEN }}
steps:
- name: Install the CLI from the API server
run: |
curl -sSL -o /usr/local/bin/argocd \
https://$ARGOCD_SERVER/download/argocd-linux-amd64
chmod +x /usr/local/bin/argocd
- name: Wait for the rollout
run: argocd app wait api --sync --health --timeout 600
- name: Report the deployed digest
if: always()
run: |
echo "### api rollout" >> $GITHUB_STEP_SUMMARY
echo "image: ${{ needs.build.outputs.image }}" >> $GITHUB_STEP_SUMMARY
argocd app get api -o json \
| jq -r '"sync: \(.status.sync.status) health: \(.status.health.status)"' \
>> $GITHUB_STEP_SUMMARYThe token in ARGOCD_AUTH_TOKEN is a project scoped JWT, and Argo CD RBAC decides what it may read (RBAC configuration). A read scoped token is enough for app wait and app get, so the verify job observes the rollout without gaining the ability to trigger one. Writing the result to $GITHUB_STEP_SUMMARY puts the deployed digest and the final sync and health status on the run page, which is where anyone investigating a bad release looks first.
Sizing
Three jobs with three different shapes. Rates are from the pricing page, billed per minute, checked on 2026-08-13.
| Label | vCPU | RAM | Storage | USD per minute | Job it fits |
|---|---|---|---|---|---|
warp-ubuntu-latest-x64-2x | 2 | 8 GB | 150GB SSD | $0.004 | Promote and verify |
warp-ubuntu-latest-x64-4x | 4 | 16 GB | 150GB SSD | $0.008 | Image build for a single service |
warp-ubuntu-latest-x64-8x | 8 | 32 GB | 150GB SSD | $0.016 | Image build with a heavy compile stage |
warp-ubuntu-latest-arm64-4x | 4 | 16 GB | 150GB SSD | $0.006 | The same build when the image publishes an arm64 manifest |
The promote job runs a checkout, a one line file edit, and a push. It is a 2 vCPU job at every fleet size, because the config repository does not grow with the number of services deploying to it. The verify job spends nearly all of its runtime blocked on the Argo CD API, so it belongs on the same row for the same reason.
Worked model for a release pipeline
Assumptions: 12 services, each merging to main and releasing on average once a weekday, 22 weekdays a month, so 264 releases. Each build job holds the runner 6 minutes, each promote job 1 minute, and each verify job 4 minutes of waiting. Substitute your own counts from the workflow run history.
| Line | Arithmetic | Monthly |
|---|---|---|
| Build jobs | 264 x 6 min x $0.008 | $12.67 |
| Promote jobs | 264 x 1 min x $0.004 | $1.06 |
| Verify jobs | 264 x 4 min x $0.004 | $4.22 |
| Total | $17.95 |
The build line is the one with a GitHub-hosted equivalent. Those 1,584 build minutes list at $0.012 per minute on the 4-core Linux larger runner, which is $19.01 (GitHub Actions billing reference, checked on 2026-08-13). Stated as list price arithmetic: warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB) costs $0.008 per minute against $0.012 per minute for the 4-core Linux larger runner (4 vCPU, 16 GB): 33 percent lower list price. GitHub list price checked on 2026-08-13.
The verify line is worth reading twice. It buys nothing except confirmation, it grows with rollout duration rather than with build complexity, and it is the first line that inflates when someone raises a timeout after a flaky release.
Bottlenecks
The polling window sits inside billed runner time
Argo CD polls every three minutes by default, so a verify job that starts the instant the promote job finishes can spend most of its life waiting for the controller to notice a commit it already has. Configure the git webhook to the Argo CD API server and that window collapses to the round trip (webhook configuration). Until then, the timeout on argocd app wait needs headroom for polling plus the rollout itself.
A commit made by GITHUB_TOKEN starts nothing
Events triggered by the repository's GITHUB_TOKEN do not create a new workflow run (triggering a workflow from a workflow). Teams that keep manifests in the same repository and expect the promote commit to start a validation workflow find that nothing fires. A deploy key or GitHub App installation token restores that behavior, and it is also the credential a separate config repository requires. Treat it as a write path into production configuration and scope it to that one repository.
Concurrent promotions collide on one file
Twelve services pushing to one config repository will race. The rebase retry in the promote job above absorbs the common case. Splitting manifests so each service owns its own overlay file keeps the rebases textual rather than semantic, which is what stops a retry from silently reverting another service's release.
Healthy means whatever the health check says
Argo CD assesses health per resource kind and supports custom health checks for resources it does not understand natively (resource health). A custom resource with no health definition can report healthy the moment it is applied, which turns argocd app wait into a check that the manifest arrived. Sync phases and waves also extend the wait, because later waves start only after earlier ones report healthy (sync phases and waves). Both are reasons the verify job's timeout is a deployment decision rather than a formality.
Floating tags quietly break the model
A manifest that references api:latest gives Argo CD nothing to diff, so the commit that was supposed to trigger a rollout changes no bytes in the repository and the controller has no work to do. Digest references are what make the commit meaningful. The alternative path, where a component watches the registry and writes the update itself rather than the pipeline doing it, is Argo CD Image Updater, and it moves the same decision out of GitHub Actions rather than removing it.
What a bigger runner does not fix
A manifest the API server rejects, a readiness probe pointed at the wrong port, or a project RBAC rule that denies the sync fails the same way on every runner size. Faster machines shorten the loop on those failures without preventing them.
Proof
Public repositories running warp- labels show these job shapes in production and the workflow file is one click away. kintsugi-tax/killbill-kintsugi-plugin runs its release workflow on warp-ubuntu-latest-x64-2x, which is the small-runner shape the promote and verify jobs use here (checked on 2026-08-13). triggerdotdev/trigger.dev runs its end-to-end matrix on warp-ubuntu-latest-x64-4x and warp-windows-latest-x64-8x, the build-sized row in the table above (checked on 2026-08-13). Both labels come from the catalog on the cloud runners page.
Every cost figure above carries its arithmetic, a source link, and a checked-on date, and the same per-minute rates appear on the pricing page. Argo CD behavior on this page is cited to the upstream documentation rather than restated from memory, so re-check those links before quoting the polling interval or the wait semantics internally.
FAQ
Does an Argo CD pipeline need cluster credentials in GitHub Actions?
No. With an automated sync policy the workflow pushes a manifest commit to the config repository and the Argo CD controller inside the cluster applies it, so the pipeline needs no direct access to the Argo CD API server and no kubeconfig. The credentials the workflow does hold are a registry credential for the image push and a git write token scoped to the config repository.
How long after the workflow pushes does Argo CD deploy the change?
Argo CD polls repositories every three minutes to detect manifest changes, so an unassisted push waits up to that long before the controller starts syncing. Configuring the API server to receive webhook events from the git provider removes the polling delay, which matters when a GitHub Actions job is billed per minute while it waits for the rollout.
What does an Argo CD release pipeline cost on WarpBuild runners?
The image build job runs on warp-ubuntu-latest-x64-4x at $0.008 per minute, and the manifest commit and rollout verification jobs run on warp-ubuntu-latest-x64-2x at $0.004 per minute, billed per minute of runtime.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.