@shinyaz

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
Terminal
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.

Terminal
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 true

Create an nginx Deployment and Service.

manifests/deployment.yaml
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: 80
Terminal
git add . && git commit -m "Initial commit: nginx deployment and service"
git push -u origin main

Add Permissions to the Capability Role

Add a codecommit:GitPull inline policy to the Capability Role. Replace 111122223333 with your AWS account ID.

Terminal
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).

nginx-codecommit.yaml
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=true
Terminal
kubectl apply -f nginx-codecommit.yaml

The 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.

Output
NAME               SYNC STATUS   HEALTH STATUS   REVISION                                   PROJECT
nginx-codecommit   Synced        Healthy         d3bd890b2ce55b3092dc422147755b8a2417c462   default

Pods and Service were running normally.

Output
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    75s

Deployment 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:

Terminal
# Change replicas: 1 → 2 and push
git commit -am "Scale nginx to 2 replicas" && git push

ArgoCD'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.

Terminal (monitor auto-sync)
# 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
done
Output (monitored every 30s)
20: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 here

Considering 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:

Terminal
kubectl get application nginx-codecommit -n argocd \
  -o jsonpath='{.status.conditions[0].message}'
Output
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:

Terminal
kubectl annotate application nginx-codecommit -n argocd \
  argocd.argoproj.io/refresh=hard --overwrite

Deployment 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.

Output (diff from Case 1)
  is not authorized to perform: codecommit:GitPull on resource:
  arn:aws:codecommit:ap-northeast-1:111122223333:argocd-demo-typo

Both 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.

codecommit-repo-secret.yaml
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: default

With 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:GitPull to 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's Resource ARN against the Application's repoURL first
  • 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=hard to trigger an immediate retry
Cleanup
Terminal
# 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

Share this post

Shinya Tahara

Shinya Tahara

Solutions Architect @ AWS

I'm a Solutions Architect at AWS, providing technical guidance primarily to financial industry customers. I share learnings about cloud architecture and AI/ML on this site.The views and opinions expressed on this site are my own and do not represent the official positions of my employer.

Related Posts