AWS Controller for Kubernetes (ACK) Getting Started

Modern Kubernetes applications often require additional infrastructure such as databases and storage buckets. Traditionally these resources are provisioned by a platform team, requiring coordination before an application can be deployed. Tools like Crossplane and Kubernetes Resource Orchestrator aim to manage infrastructure via Kubernetes objects. KRO does this using providers — for AWS resources the provider is AWS Controllers for Kubernetes (ACK).
AWS Controllers for Kubernetes (ACK) lets you define and use AWS service resources directly from Kubernetes. ACK can be used standalone on any EKS cluster or together with tools like KRO.
In this example, I demonstrate how to install ACK controllers using Pod Identity instead of IRSA (IAM Roles for Service Accounts).
Getting Started

Step 1. Provision EKS with Pod Identity enabled
To follow along you will need an EKS cluster with Pod Identity enabled. Source code in Terraform is available in my GitRepo aws-eks-terraform/eks-auto-ACK.
Step 2. Create IAM role with Pod Identity for the IAM controller
Each ACK service controller is packaged as a separate container image published in a public repository. Container images and Helm charts for all ACK service controllers can be found in the ACK registry within the Amazon ECR Public Gallery.
The IAM controller requires an IAM role with permissions to create and manage IAM roles. The example below creates the role with a Pod Identity trust policy using Terraform.
ack-role-iam-podid.tf
data "aws_iam_policy_document" "pod_id_assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["pods.eks.amazonaws.com"]
}
actions = [
"sts:AssumeRole",
"sts:TagSession"
]
}
}
resource "aws_iam_role" "ack-iam-controller" {
name = "ack-iam-controller"
assume_role_policy = data.aws_iam_policy_document.pod_id_assume_role.json
}
resource "aws_iam_policy" "ack-iam-controller" {
name = "ack-iam-controller-policy"
description = "Policy for ACK IAM Controller"
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRoleCreation",
"Effect": "Allow",
"Action": [
"iam:CreateRole",
"iam:TagRole",
"iam:PassRole",
"iam:UpdateAssumeRolePolicy",
"iam:GetRole",
"iam:ListRolePolicies",
"iam:PutRolePolicy",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:ListRoles",
"iam:ListRoleTags",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListPolicies",
"iam:GetPolicy",
"iam:GetPolicyVersion"
],
"Resource": "*"
},
{
"Sid": "DenyAdministratorAccessAttachment",
"Effect": "Deny",
"Action": ["iam:AttachRolePolicy", "iam:DetachRolePolicy"],
"Resource": "arn:aws:iam::aws:policy/AdministratorAccess"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ack-iam-controller" {
role = aws_iam_role.ack-iam-controller.name
policy_arn = aws_iam_policy.ack-iam-controller.arn
}
resource "aws_eks_pod_identity_association" "ack-iam-controller" {
cluster_name = module.eks.cluster_name
namespace = "ack-system"
service_account = "ack-iam-controller"
role_arn = aws_iam_role.ack-iam-controller.arn
}
Step 3. Install the IAM controller chart
The IAM chart requires aws.region to be set. The snippet below fetches the latest chart version automatically.
export SERVICE=iam
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/${SERVICE}-controller/releases/latest | jq -r '.tag_name | ltrimstr("v")')
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=eu-west-1
aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws
helm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller \
oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION --set=aws.region=$AWS_REGION
Step 4. Create a sample IAM role
Create example-role and verify it appears in AWS.
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Role
metadata:
name: example-role
spec:
name: example-role
assumeRolePolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession"
]
}
]
}
description: "Example role created by ACK"
maxSessionDuration: 3600
policies:
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Step 5. Provision additional controllers
With the IAM controller installed, you can provision further ACK controllers directly from Kubernetes manifests — no Terraform needed.
The example below adds the S3 controller using IRSA (IAM Roles for Service Accounts):
- Use ACK to create an IRSA-style IAM role for the S3 controller
- Install the S3 controller Helm chart, annotating its ServiceAccount with the role ARN
Create an IRSA IAM role for the S3 controller
ack-s3-iam-rsa.yaml
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Role
metadata:
name: ack-s3-controller
spec:
name: ack-s3-controller
assumeRolePolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS-Account-id>:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/87AD44391B740265AE0AB65382E8D918"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-1.amazonaws.com/id/87AD44391B740265AE0AB65382E8D918:sub": "system:serviceaccount:ack-system:ack-s3-controller"
}
}
}
]
}
description: "S3 role created by ACK"
maxSessionDuration: 3600
policies:
- "arn:aws:iam::aws:policy/AmazonS3FullAccess"
Install the S3 controller
[!NOTE] When using IRSA, the ServiceAccount must be annotated with the IAM Role ARN.
Create a values file to set the region and ServiceAccount annotation:
s3-controller-values.yaml
aws:
region: "eu-west-1"
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::<AWS-account-ID>:role/ack-s3-controller"
Install the S3 controller using the Helm chart and the values file:
export SERVICE=s3
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/${SERVICE}-controller/releases/latest | jq -r '.tag_name | ltrimstr("v")')
helm install -n ack-system ack-$SERVICE-controller \
oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION -f s3-controller-values.yaml
Provision a sample bucket
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
name: example-ack-bucket-sample
spec:
name: example-ack-bucket-051020251400
versioning:
status: Enabled
tagging:
tagSet:
- key: environment
value: dev
- key: owner
value: "Denny Vettom"
Step 6. Deploy a sample app with S3 access
Scenario: Deploy an application that requires access to an S3 bucket. Configure bucket permissions and IRSA using ACK.
- Create an IAM policy granting S3 access using ACK
- Create an IRSA role and attach the policy using ACK
- Deploy the app with the configured ServiceAccount and verify access
ack-demo-iam-rsa.yaml
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Policy
metadata:
name: demo-app-bucket-access-policy
spec:
name: demo-app-bucket-access-policy
description: "Grants read/write access to the demo S3 bucket"
policyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket", "s3:PutObject"],
"Resource": [
"arn:aws:s3:::eks-auto-demo-bucket-102025",
"arn:aws:s3:::eks-auto-demo-bucket-102025/*"
]
}
]
}
---
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Role
metadata:
name: ack-demo-app-role
spec:
name: ack-demo-app-role
assumeRolePolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS-Account-id>:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/87AD44391B740265AE0AB65382E8D918"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-1.amazonaws.com/id/87AD44391B740265AE0AB65382E8D918:sub": "system:serviceaccount:default:ack-demo-app-sa"
}
}
}
]
}
description: "Demo app role created by ACK"
maxSessionDuration: 3600
policies:
- arn:aws:iam::<AWS-Account-id>:policy/demo-app-bucket-access-policy
Apply the configuration:
Deploy the demo app
This deploys a pod with the AWS CLI installed. No credentials are configured — authentication is provided by IRSA via the annotated ServiceAccount.
deploy-ack-demo-app.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ack-demo-app-sa
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: ack-demo-app
name: ack-demo-app
spec:
replicas: 1
selector:
matchLabels:
app: ack-demo-app
strategy: {}
template:
metadata:
labels:
app: ack-demo-app
spec:
serviceAccountName: ack-demo-app-sa
containers:
- image: dennysv/dvawscli
name: dvawscli
resources: {}
Verify bucket access
Exec into the pod and test S3 access:
kubectl exec -it deployment/ack-demo-app -- bash
$ aws s3 ls eks-auto-demo-bucket-102025/
$ aws s3 cp /etc/os-release s3://eks-auto-demo-bucket-102025/
$ aws s3 ls eks-auto-demo-bucket-102025/
$ aws s3 rm s3://eks-auto-demo-bucket-102025/os-release
You have successfully configured ACK and deployed a sample app that manages its own AWS infrastructure through Kubernetes manifests.