AWS Builder Center
AWS Runs ArgoCD for You, How Do You Actually See What It's Doing?

AWS Runs ArgoCD for You, How Do You Actually See What It's Doing?

The EKS managed ArgoCD capability removes the operational burden of running ArgoCD yourself, but it also means you no longer have kubectl access to the ArgoCD control plane. Here's how to wire its logs into CloudWatch so you can actually tell what's happening when something goes wrong.

My last article walked through enabling AWS's managed ArgoCD capability on EKS. Here is the link . At the end of it, you had a fully working GitOps setup where AWS runs the ArgoCD control plane for you and you just describe what you want deployed. I called out one trade-off at the time: ArgoCD's control plane lives in an AWS-owned account, not your cluster, so the usual kubectl -n argocd get pods and kubectl logs approach to debugging does not work anymore.
That was fine for a getting-started walkthrough, but it leaves a real question unanswered: what do you do when a sync fails and you can't see the ArgoCD controller logs? In a self-hosted setup you'd just tail the application-controller pod. Here, there's no pod to tail. AWS quietly answered this in June 2026 by adding CloudWatch Vended Logs support for EKS Capabilities. You can now route the managed ArgoCD controller logs to a CloudWatch log group, an S3 bucket, or Kinesis Data Firehose. This article wires them into CloudWatch Logs and shows what you can do with them once they're there.

My setup for this walkthrough (Windows, PowerShell as the terminal):

1
2
3
4
5
6
7
8
9
10
11
aws configure list-profiles
terraform-dev

eksctl version
0.224.0

kubectl version
Client Version: v1.33.0
Kustomize Version: v5.6.0

$env:AWS_PROFILE="terraform-dev"
This picks up from the same argocd-demo cluster and my-argocd capability from the previous article. If you're starting fresh, recreate those first (the previous article has every command). I'm working in us-east-1.

What we're building

  1. Retrieve the capability ARN needed to configure log delivery
  2. Create delivery sources for the ArgoCD application controller and repo server log types
  3. Create a CloudWatch log group and delivery destination
  4. Wire sources to destination with create-delivery
  5. Trigger real sync activity (including a deliberate failure) to generate log entries
  6. Run CloudWatch Logs Insights queries against the live logs
  7. Clean everything up

Step 1: Grab the capability ARN

Log delivery for EKS Capabilities is configured through the CloudWatch Logs delivery API, and the central piece of that API is the capability's ARN. You'll need it for every delivery source you create, so get it once and store it.
1
2
3
4
5
6
7
8
9
$CAPABILITY_ARN = aws eks describe-capability `
--region us-east-1 `
--cluster-name argocd-demo `
--capability-name my-argocd `
--query 'capability.arn' `
--output text `
--profile terraform-dev

Write-Host "Capability ARN: $CAPABILITY_ARN"
Output:
1
Capability ARN: arn:aws:eks:us-east-1:234617061868:capability/argocd-demo/argocd/my-argocd/b6d06678-fb02-2b8b-5eef-93df8aa3d169

Step 2: Create delivery sources for the log types you want

Here's the part that tripped me up reading the docs: log delivery for ArgoCD is not a single "turn logging on" toggle at the capability level. ArgoCD breaks its logs into five separate components, and you configure a delivery source for each one you care about.
ComponentLog type string
Application ControllerEKS_CAPABILITY_ARGOCD_APPLICATION_LOGS
ApplicationSet ControllerEKS_CAPABILITY_ARGOCD_APPLICATIONSET_LOGS
Commit ServerEKS_CAPABILITY_ARGOCD_COMMITSERVER_LOGS
Repo ServerEKS_CAPABILITY_ARGOCD_REPOSERVER_LOGS
ArgoCD ServerEKS_CAPABILITY_ARGOCD_SERVER_LOGS
For observability and sync debugging, the Application Controller and Repo Server are the two most useful. The Application Controller is what reconciles application state and runs the sync operations. The Repo Server is what actually fetches and renders the manifests from your Git repo. Enable both.
Application Controller source:
1
2
3
4
5
6
aws logs put-delivery-source `
--name argocd-demo-application-logs `
--resource-arn $CAPABILITY_ARN `
--log-type EKS_CAPABILITY_ARGOCD_APPLICATION_LOGS `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
11
{
"deliverySource": {
"name": "argocd-demo-application-logs",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery-source:argocd-demo-application-logs",
"resourceArns": [
"arn:aws:eks:us-east-1:234617061868:capability/argocd-demo/argocd/my-argocd/b6d06678-fb02-2b8b-5eef-93df8aa3d169"
],
"service": "eks",
"logType": "EKS_CAPABILITY_ARGOCD_APPLICATION_LOGS"
}
}
Repo Server source:
1
2
3
4
5
6
aws logs put-delivery-source `
--name argocd-demo-reposerver-logs `
--resource-arn $CAPABILITY_ARN `
--log-type EKS_CAPABILITY_ARGOCD_REPOSERVER_LOGS `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
11
{
"deliverySource": {
"name": "argocd-demo-reposerver-logs",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery-source:argocd-demo-reposerver-logs",
"resourceArns": [
"arn:aws:eks:us-east-1:234617061868:capability/argocd-demo/argocd/my-argocd/b6d06678-fb02-2b8b-5eef-93df8aa3d169"
],
"service": "eks",
"logType": "EKS_CAPABILITY_ARGOCD_REPOSERVER_LOGS"
}
}

Step 3: Create the log group and delivery destination

A delivery destination in CloudWatch Logs terms is the target — your log group, an S3 bucket, or a Firehose stream. We'll use CloudWatch Logs so we can run Logs Insights queries against the data directly.
Create the log group first:
1
2
3
4
aws logs create-log-group `
--log-group-name /eks/argocd-demo/capability-logs `
--region us-east-1 `
--profile terraform-dev
Get your account ID, then register the log group as a delivery destination:
1
2
3
4
5
6
7
8
9
10
$ACCOUNT_ID = aws sts get-caller-identity `
--query Account `
--output text `
--profile terraform-dev

aws logs put-delivery-destination `
--name argocd-demo-cw-destination `
--delivery-destination-configuration destinationResourceArn="arn:aws:logs:us-east-1:${ACCOUNT_ID}:log-group:/eks/argocd-demo/capability-logs" `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
{
"deliveryDestination": {
"name": "argocd-demo-cw-destination",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery-destination:argocd-demo-cw-destination",
"deliveryDestinationType": "CWL",
"deliveryDestinationConfiguration": {
"destinationResourceArn": "arn:aws:logs:us-east-1:234617061868:log-group:/eks/argocd-demo/capability-logs"
}
}
}
Attach the resource policy that lets the log delivery service write to the log group. Create a policy file first:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@"
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEKSVendedLogDelivery",
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": "logs:PutLogEvents",
"Resource": "arn:aws:logs:us-east-1:${ACCOUNT_ID}:log-group:/eks/argocd-demo/capability-logs:*"
}
]
}
"@
| Set-Content -Path .\argocd-logs-dest-policy.json

aws logs put-delivery-destination-policy `
--delivery-destination-name argocd-demo-cw-destination `
--delivery-destination-policy file://argocd-logs-dest-policy.json `
--region us-east-1 `
--profile terraform-dev

Step 4: Wire sources to destination

This is the step that actually connects what we created in Steps 2 and 3. Retrieve the destination ARN from the previous output, then create a delivery for each source.
1
2
3
4
5
6
7
8
9
10
11
12
$DEST_ARN = aws logs get-delivery-destination `
--name argocd-demo-cw-destination `
--query 'deliveryDestination.arn' `
--output text `
--region us-east-1 `
--profile terraform-dev

aws logs create-delivery `
--delivery-source-name argocd-demo-application-logs `
--delivery-destination-arn $DEST_ARN `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"delivery": {
"id": "kXyTI125XJdBlPDU",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery:kXyTI125XJdBlPDU",
"deliverySourceName": "argocd-demo-application-logs",
"deliveryDestinationArn": "arn:aws:logs:us-east-1:234617061868:delivery-destination:argocd-demo-cw-destination",
"deliveryDestinationType": "CWL",
"recordFields": [
"resource_arn",
"event_timestamp",
"stream",
"level",
"message",
"error",
"application",
"app-namespace",
"project",
"dest-server",
"dest-namespace",
"dest-name",
"comparison-level",
"time_ms",
"patch_ms",
"setop_ms",
"compare_app_state_ms",
"refresh_app_conditions_ms",
"process_refresh_app_conditions_errors_ms",
"comparison_with_nothing_ms",
"name",
"namespace",
"app_status_update_ms",
"auto_sync_ms",
"diff_ms",
"git_ms",
"health_ms",
"live_ms",
"manifests_ms",
"repo_ms",
"sync_ms",
"cluster",
"syncId",
"duration",
"eventType",
"kind"
1
2
3
4
5
aws logs create-delivery `
--delivery-source-name argocd-demo-reposerver-logs `
--delivery-destination-arn $DEST_ARN `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"delivery": {
"id": "2HHmI6SUZbjtNIaP",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery:2HHmI6SUZbjtNIaP",
"deliverySourceName": "argocd-demo-reposerver-logs",
"deliveryDestinationArn": "arn:aws:logs:us-east-1:234617061868:delivery-destination:argocd-demo-cw-destination",
"deliveryDestinationType": "CWL",
"recordFields": [
"resource_arn",
"event_timestamp",
"stream",
"level",
"message",
"error",
"grpc.code",
"grpc.method",
"grpc.service",
"grpc.time_ms",
"grpc.method_type",
"grpc.component",
"grpc.error",
"version",
"component",
"duration",
"execID"
]
}
}
Confirm both deliveries are active:
1
2
3
aws logs describe-deliveries `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"deliveries": [
{
"id": "kXyTI125XJdBlPDU",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery:kXyTI125XJdBlPDU",
"deliverySourceName": "argocd-demo-application-logs",
"deliveryDestinationArn": "arn:aws:logs:us-east-1:234617061868:delivery-destination:argocd-demo-cw-destination",
"deliveryDestinationType": "CWL"
},
{
"id": "2HHmI6SUZbjtNIaP",
"arn": "arn:aws:logs:us-east-1:234617061868:delivery:2HHmI6SUZbjtNIaP",
"deliverySourceName": "argocd-demo-reposerver-logs",
"deliveryDestinationArn": "arn:aws:logs:us-east-1:234617061868:delivery-destination:argocd-demo-cw-destination",
"deliveryDestinationType": "CWL"
}
]
}

Step 5: Generate some real log traffic

With logging wired up, let's give ArgoCD something to log. First, force a refresh of the guestbook app. This triggers the application controller to reconcile state and logs the result.
1
2
3
4
kubectl annotate application guestbook `
argocd.argoproj.io/refresh=hard `
-n argocd `
--overwrite
Output:
1
application.argoproj.io/guestbook annotated
Now do something more interesting: deliberately cause a sync failure so we can see what that looks like in the logs. Edit guestbook-app.yaml and change path: guestbook to path: guestbook-does-not-exist, then apply it.
1
kubectl apply -f guestbook-app.yaml
Output:
1
application.argoproj.io/guestbook unchanged
Check application status:
1
kubectl get application guestbook -n argocd
Output:
1
2
NAME SYNC STATUS HEALTH STATUS
guestbook Synced Healthy
You'll see it go into an error state. In a self-hosted ArgoCD setup, you'd go look at the repo-server pod logs right now. Here, you wait for CloudWatch. Restore the correct path after a minute or two:
1
2
# Edit guestbook-app.yaml back to path: guestbook
kubectl apply -f guestbook-app.yaml

Step 6: Query the logs in CloudWatch Logs Insights

Give it about 2-5 minutes after the sync activity, then check whether log streams have appeared:
1
2
3
4
5
6
aws logs describe-log-streams `
--log-group-name /eks/argocd-demo/capability-logs `
--order-by LastEventTime `
--descending `
--region us-east-1 `
--profile terraform-dev
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"logStreams": [
{
"logStreamName": "EKS_eks_capability_argocd_application_logs_argocd-demo_argocd_my-argocd_b6d06678-fb02-2b8b-5eef-93df8aa3d169",
"creationTime": 1790162084669,
"firstEventTimestamp": 1790162079315,
"lastEventTimestamp": 1790162079321,
"lastIngestionTime": 1790162084738,
"uploadSequenceToken": "49039859687242003493905590366356193954449376915722972655",
"arn": "arn:aws:logs:us-east-1:234617061868:log-group:/eks/argocd-demo/capability-logs:log-stream:EKS_eks_capability_argocd_application_logs_argocd-demo_argocd_my-argocd_b6d06678-fb02-2b8b-5eef-93df8aa3d169",
"storedBytes": 0
},
{
"logStreamName": "EKS_eks_capability_argocd_reposerver_logs_argocd-demo_argocd_my-argocd_b6d06678-fb02-2b8b-5eef-93df8aa3d169",
"creationTime": 1790161842835,
"firstEventTimestamp": 1790161836258,
"lastEventTimestamp": 1790161839177,
"lastIngestionTime": 1790161842861,
"uploadSequenceToken": "49039859687241681984225653898259604483879275579890314749",
"arn": "arn:aws:logs:us-east-1:234617061868:log-group:/eks/argocd-demo/capability-logs:log-stream:EKS_eks_capability_argocd_reposerver_logs_argocd-demo_argocd_my-argocd_b6d06678-fb02-2b8b-5eef-93df8aa3d169",
"storedBytes": 0
}
]
}
Once streams are there, open the CloudWatch console, go to Logs Insights, and select /eks/argocd-demo/capability-logs. ArgoCD logs in structured JSON, so Logs Insights will automatically discover the fields (check the "Discovered fields" panel on the right to see the actual field names from your log data).
To find error-level entries from the application controller:
1
2
3
4
filter level = "error"
| fields @timestamp, level, msg, app, controller
| sort @timestamp desc
| limit 50
To isolate sync failures:
1
2
3
4
filter level = "error" and (msg like /sync/ or msg like /failed/)
| fields @timestamp, msg, app, controller
| sort @timestamp desc
| limit 50
To find except error level
1
2
3
4
filter level != "error"
| fields @timestamp, level, msg, app, controller
| sort @timestamp desc
| limit 50
To get a summary of log volume by level (useful as a CloudWatch dashboard widget):
1
2
stats count() as total by level
| sort total desc
One thing worth knowing before you finalize those queries: the exact field names (level, msg, app, controller) are based on ArgoCD's standard structured logging format, but confirm them against the "Discovered fields" panel once real logs are in the group. Logs Insights will show you exactly what fields arrived.

What you get out of the box

  • Logs from the ArgoCD components that actually matter for debugging (application controller and repo server), routed to wherever you already send your logs
  • CloudWatch Logs Insights queries against structured JSON log output, with automatic field discovery
  • Five granular log types per capability so you can enable only what you need and keep the noise down
  • Per-event-volume Vended Logs pricing, which is typically lower than standard CloudWatch ingestion for high-volume log sources

What you're trading away

  • You're still waiting a few minutes for logs to appear in CloudWatch, not tailing a pod in real time. For rapid iteration during an incident, that lag matters.
  • The exact log field names depend on how AWS surfaces them at the delivery layer. ArgoCD uses structured JSON logging internally, but you need to confirm what actually arrives in your log group before you can write reliable Logs Insights queries. The "Discovered fields" panel is your friend here.
  • Setting up delivery involves three separate API calls with three separate resource names to keep track of. It's not complicated, but it's more moving parts than flipping a single switch.
  • Five log types per capability means more decisions about what to enable and more cost if you enable all five at scale.

My thoughts

If you're running the managed ArgoCD capability, enabling CloudWatch Vended Logs for at least the Application Controller and Repo Server log types is a low-effort way to close the observability gap that the managed setup creates. You don't get a live pod to tail, but you do get queryable structured logs that tell you what the ArgoCD controller actually did and why it failed, which is what you need in practice.
The pattern here is the same one you'll see across other EKS Capabilities. ACK and kro use an identical three-step delivery API with their own log type strings. Once you've done it for ArgoCD, you know how to do it for everything else in that ecosystem. That's probably the direction AWS is heading with this: a uniform observability layer across managed platform components, routed through the same CloudWatch pipelines you already have.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article