Skip to main content
ArgoCD is the dominant GitOps controller for Kubernetes — it watches a git repository, compares desired state to live cluster state, and reconciles the difference automatically. But to register an application with ArgoCD, you create an Application CRD in the cluster. That CRD needs to know where the application’s manifests live (the git repo and path), where to deploy them (the cluster endpoint), and any runtime configuration (image tags, connection strings, environment variables) that differs per deployment. These runtime values — like an ECR repository URL — are infrastructure outputs that only exist after cloud resources are provisioned. The sync "argocd" block handles exactly this: it emits an ArgoCD Application CRD, accepts a values map for per-deployment config, and sequences the CRD creation after any pending infrastructure outputs it references.

What you’ll learn

  • The sync "argocd" block syntax — repo, path, target, namespace, values
  • How pending infrastructure outputs (like an ECR URL) wire into ArgoCD app config
  • How sync "argocd" differs from deploy "helm" — GitOps pull vs Helm push

Why this matters

deploy "helm" pushes a Helm release directly to the cluster. sync "argocd" tells ArgoCD where to find the manifests and what runtime values to inject — ArgoCD then pulls and reconciles continuously. Use deploy for imperative releases; use sync for GitOps-managed continuous reconciliation.

The source code

# ECR repository — repositoryUrl is Pending<string>.
unit "aws_ecr_repository" "app" {
  name = "myapp-backend"
}

# sync "argocd" emits an argoproj.io/v1alpha1 Application CRD.
# The image_tag value is Pending<string> — wired from the ECR repository URL.
# ubx sequences the Application CRD creation after the ECR repo exists.
sync "argocd" "backend" {
  repo      = "https://github.com/myorg/app"
  path      = "k8s"
  target    = "https://kubernetes.default.svc"
  namespace = "argocd"
  values = {
    image_tag = unit.aws_ecr_repository.app.repositoryUrl  # Pending<string>
    env       = input.env                                    # Resolved<string>
    replicas  = 2                                            # Resolved<int>
  }
}

How it works

1

sync emits an ArgoCD Application CRD

sync "argocd" compiles to a Pulumi kubernetes.apiextensions.CustomResource call that creates an argoproj.io/v1alpha1/Application object in the cluster. The repo, path, target, and namespace attributes map directly to ArgoCD Application spec fields.
2

values are passed as Helm parameters to ArgoCD

The values map compiles to ArgoCD’s spec.source.helm.parameters array — key/value pairs that ArgoCD injects into the Helm chart when it renders manifests. Each entry becomes a { name, value } parameter in the Application spec.
3

Pending values sequence the CRD after infrastructure

unit.aws_ecr_repository.app.repositoryUrl is Pending<string> — the Application CRD can’t be created until the ECR repository exists and has a URL. ubx generates .apply() wrapping to sequence the CRD creation after the ECR resource.

What ubx generates

// Generated by ubx — do not edit
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as k8s from "@pulumi/kubernetes";

const config = new pulumi.Config();
const env = config.get("env") || "dev";

const app = new aws.ecr.Repository("app", { name: "myapp-backend" });

// Sequenced after app.repositoryUrl resolves
const backend = app.repositoryUrl.apply(repoUrl =>
    new k8s.apiextensions.CustomResource("backend", {
        apiVersion: "argoproj.io/v1alpha1",
        kind: "Application",
        metadata: { name: "backend", namespace: "argocd" },
        spec: {
            source: {
                repoURL: "https://github.com/myorg/app",
                path: "k8s",
                helm: {
                    parameters: [
                        { name: "image_tag", value: repoUrl },
                        { name: "env",       value: env },
                        { name: "replicas",  value: "2" },
                    ],
                },
            },
            destination: { server: "https://kubernetes.default.svc" },
            syncPolicy: { automated: { prune: true, selfHeal: true } },
        },
    })
);

export const ecrUrl = app.repositoryUrl;

Common mistakes

sync "argocd" requires the ArgoCD CRD (Application) to already be installed in the cluster before ubx apply runs. If ArgoCD is not installed, the apply will fail with a CRD-not-found error. Install ArgoCD first (e.g. via a deploy "helm" block for the ArgoCD chart itself) and ensure it’s a dependency of this sync block.
ArgoCD Application values are passed as Helm parameters — key/value pairs injected at render time. They override values in the chart’s values.yaml. Make sure the keys match the parameter names your Helm chart expects, not arbitrary strings.

Run it

ubx validate   # type-checks Pending<T> references
ubx plan       # requires AWS + Kubernetes credentials with ArgoCD installed
ubx apply      # creates ECR repo, then registers ArgoCD Application

What you learned

sync "argocd" emits an ArgoCD Application CRD with repo, path, target, and Helm parameters
Pending infrastructure outputs wire into ArgoCD values with the same bare reference syntax as deploy "helm"
ArgoCD must be installed in the cluster before the Application CRD can be created

Next steps

sync Flux kustomization

GitOps with Flux instead of ArgoCD

sync block reference

Full sync block syntax for both ArgoCD and Flux