EKS ArgoCD Capability — Deploy from a CodeCommit Private Repository via GitOps
Table of Contents
Introduction
In the previous two posts, I set up EKS ArgoCD Capability and deployed from a public GitHub repository (the guestbook sample). In real projects, however, deployment manifests live in private repositories.
EKS ArgoCD Capability supports "direct integration" with CodeCommit. Direct integration means specifying the CodeCommit URL directly in the Application's repoURL without creating a Repository Secret (a Kubernetes Secret that stores repository authentication information). By adding codecommit:GitPull to the Capability Role, authentication is handled transparently through IAM. Unlike self-hosted ArgoCD, which requires managing Git credentials as Kubernetes Secrets, the Capability version handles authentication entirely through IAM.
This post walks through building a GitOps deployment from a CodeCommit private repository using direct integration, and examines error behavior when things go wrong. See the official documentation at Configure repository access.
Prerequisites:
- EKS cluster with ArgoCD Capability set up (series part 1)
- Target cluster registered
- AWS CLI, kubectl, and git configured
- Test region: ap-northeast-1
Verification 1: Deploy from a Private Repository via Direct Integration
Prepare a CodeCommit Repository
Create a CodeCommit repository and push nginx Deployment + Service manifests.
CodeCommit repository setup steps
aws codecommit create-repository \
--repository-name argocd-demo \
--repository-description "Demo repository for ArgoCD Capability"Clone it locally and configure the Git credential helper. This is required for CodeCommit HTTPS access.
git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/argocd-demo
cd argocd-demo
git config credential.helper '!aws codecommit credential-helper $@'
git config credential.UseHttpPath trueCreate an nginx Deployment and Service.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80git add . && git commit -m "Initial commit: nginx deployment and service"
git push -u origin mainAdd Permissions to the Capability Role
Add a codecommit:GitPull inline policy to the Capability Role. Replace 111122223333 with your AWS account ID.
aws iam put-role-policy \
--role-name ArgoCDCapabilityRole \
--policy-name CodeCommitGitPull \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "codecommit:GitPull",
"Resource": "arn:aws:codecommit:ap-northeast-1:111122223333:argocd-demo"
}
]
}'Note that Resource is scoped to the specific repository ARN. You could use * to grant access to all repositories in the account, but following the principle of least privilege, scope it to only the repositories you need.
Create the Application and Deploy
Create an Application with the CodeCommit HTTPS URL as repoURL. No Repository Secret is needed. The destination.name should match the target cluster name registered in series part 1 (local-cluster).
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-codecommit
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/argocd-demo
targetRevision: main
path: manifests
destination:
name: local-cluster
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=truekubectl apply -f nginx-codecommit.yamlThe finalizers field ensures deployed resources are cascade-deleted when the Application is removed (a lesson from series part 2). syncPolicy.automated enables automatic sync on Git changes: prune: true removes resources from the cluster when they're deleted from Git, and selfHeal: true reverts manual cluster changes to match Git state. These are convenient for testing, but in production, enable prune carefully to avoid unintended deletions.
After about a minute, the Application reached Synced / Healthy.
NAME SYNC STATUS HEALTH STATUS REVISION PROJECT
nginx-codecommit Synced Healthy d3bd890b2ce55b3092dc422147755b8a2417c462 defaultPods and Service were running normally.
NAME READY STATUS RESTARTS AGE
nginx-fd956d49d-snhfv 1/1 Running 0 75s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.100.146.27 <none> 80/TCP 75sDeployment from a CodeCommit private repository succeeded without any Repository Secret. Authentication was handled transparently through the Capability Role's IAM policy.
Auto-Sync on Manifest Changes
I verified that the GitOps "push and it deploys" cycle works with CodeCommit integration. After changing replicas from 1 to 2 and pushing:
# Change replicas: 1 → 2 and push
git commit -am "Scale nginx to 2 replicas" && git pushArgoCD's default polling interval is about 3 minutes. Monitoring every 30 seconds, the new revision was picked up about 5 minutes 15 seconds after the push, and pods scaled to 2.
# Check revision and pod count every 30 seconds
while true; do
rev=$(kubectl get application nginx-codecommit -n argocd \
-o jsonpath='{.status.sync.revision}' 2>/dev/null)
pods=$(kubectl get pods -n default --no-headers 2>/dev/null | wc -l)
echo "$(date +%H:%M:%S) revision=${rev:0:8} pods=$pods"
sleep 30
done20:54:12 revision=d3bd890b pods=1
20:54:44 revision=d3bd890b pods=1
...(omitted)...
20:58:25 revision=d3bd890b pods=1
20:58:56 revision=c03946cd pods=2 ← synced hereConsidering the polling interval (~3 min), ~5 minutes is within expectations. The exact time varies depending on when the push lands relative to the polling cycle. For faster feedback, consider configuring webhooks.
Verification 2: Error Behavior and Debugging on Misconfiguration
With direct integration, the repository doesn't appear in ArgoCD UI's Settings → Repositories. The Repositories screen is built from Kubernetes Secrets labeled argocd.argoproj.io/secret-type: repository, so without a Repository Secret, ArgoCD has no record of the repository. Connection testing from the UI isn't possible either.
So where do you look when something goes wrong? I reproduced two common mistakes to find out.
Case 1: Deploying Before Granting Permissions
When I created the Application without the codecommit:GitPull policy, the following error appeared in status.conditions:
kubectl get application nginx-codecommit -n argocd \
-o jsonpath='{.status.conditions[0].message}'Failed to load target state: failed to generate manifest for source 1 of 1:
rpc error: code = Unknown desc = failed to list refs: authorization failed:
<AccessDeniedException>
<Message>User: arn:aws:sts::111122223333:assumed-role/ArgoCDCapabilityRole/...
is not authorized to perform: codecommit:GitPull on resource:
arn:aws:codecommit:ap-northeast-1:111122223333:argocd-demo
because no identity-based policy allows the codecommit:GitPull action</Message>
</AccessDeniedException>The error message is highly specific — it clearly states which role, which action, and which resource was denied. IAM errors propagate directly into ArgoCD conditions, providing sufficient information for debugging.
However, the Application doesn't recover immediately after adding the permission. In this test, the same error persisted even after attaching the IAM policy and waiting about 90 seconds. ArgoCD has a backoff mechanism for retry intervals on errors, which likely explains the delay. A manual hard refresh triggers an immediate retry:
kubectl annotate application nginx-codecommit -n argocd \
argocd.argoproj.io/refresh=hard --overwriteDeployment started about 15 seconds after the hard refresh.
Case 2: Typo in the Repository URL
I created an Application with a non-existent repository name (argocd-demo-typo) in the repoURL to test the error. The same AccessDeniedException was returned — only the resource ARN in the error message changed to argocd-demo-typo.
is not authorized to perform: codecommit:GitPull on resource:
arn:aws:codecommit:ap-northeast-1:111122223333:argocd-demo-typoBoth URL typos and missing permissions produce the same AccessDeniedException. In this test, the IAM policy's Resource was scoped to a specific repository ARN, so any repository name outside that scope was treated as a permission error. You won't see a "repository not found" error.
This can be confusing during debugging. When you see AccessDeniedException, the first step should be comparing the Resource ARN in your IAM policy against the repoURL in your Application to verify the repository name matches.
Note: Using Repository Secrets Instead
As an alternative to direct integration, you can create a Repository Secret to explicitly register the CodeCommit repository with ArgoCD.
apiVersion: v1
kind: Secret
metadata:
name: codecommit-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/argocd-demo
project: defaultWith a Repository Secret, the repository appears in ArgoCD UI's Settings → Repositories. According to the official documentation, the UI shows connection status and any authentication errors.
When to choose which:
- Direct integration — Minimal setup. One IAM policy and you're done. Best when you have few repositories and your team is comfortable with IAM
- Repository Secret — Better visibility in ArgoCD UI. Preferred when managing many repositories or when you want connection status at a glance
In both cases, authentication is handled by the Capability Role's IAM policy. The Repository Secret simply makes ArgoCD "aware" of the repository's existence.
Summary
- One IAM policy is all you need for CodeCommit integration — Adding
codecommit:GitPullto the Capability Role enables private repository deployments without Repository Secrets. The Git credential management required by self-hosted ArgoCD is eliminated entirely - URL typos and missing permissions produce the same error — In this test, with the IAM policy scoped to a specific repository ARN, non-existent repositories also returned
AccessDeniedException. When debugging, always cross-check the IAM policy'sResourceARN against the Application'srepoURLfirst - Manual refresh is recommended after fixing permissions — In this test, the Application didn't auto-recover even after waiting about 90 seconds post-policy attachment. Use
argocd.argoproj.io/refresh=hardto trigger an immediate retry
Cleanup
# Delete the Application (finalizer ensures cascading deletion)
kubectl delete application nginx-codecommit -n argocd
# Delete the IAM policy
aws iam delete-role-policy \
--role-name ArgoCDCapabilityRole \
--policy-name CodeCommitGitPull
# Delete the CodeCommit repository
aws codecommit delete-repository \
--repository-name argocd-demo \
--region ap-northeast-1