Deploy a Private ECR Image on EKS

Run a private ECR image on a NATless Amazon EKS Fargate cluster.

Introduction

30 Second Summary

A locked front door protects a building only when every side entrance is secure. Online systems face the same problem when hidden public routes remain open.

In this project, you will deploy a private Amazon Elastic Container Registry (Amazon ECR) image to a private-only Amazon Elastic Kubernetes Service (Amazon EKS) cluster powered by AWS Fargate. Private VPC endpoints carry the control-plane connection plus image pull without a NAT gateway.

What You'll Build

You'll demo a Running application Pod with a private VPC address while clean image-pull events show that its private ECR image used no public fallback path.

By the end of this project, you'll have:

  • A private cluster connection that reaches the Kubernetes API through AWS CloudShell or a private AWS Systems Manager Session Manager host.
  • A CoreDNS recovery that turns Pending system Pods into Running Fargate workloads.
  • A private image deployment whose private address and clean image-pull events prove the workload has no public fallback path.
  • Secret Mission: Scale the application to two Fargate Pods, compare their private placement, and return to one replica.

Are there any prerequisites?

You need an AWS identity that can manage VPC endpoints, IAM roles, EKS clusters, and Fargate profiles.

You also need an existing us-east-1 VPC with private subnets plus a directly pushed Linux amd64 image in private Amazon ECR.

Before We Start

Before the hands-on work begins, lock in the private deployment path you are building. You will deploy your existing Linux amd64 image from private Amazon ECR to Amazon EKS in us-east-1 without a NAT gateway or public Kubernetes API endpoint.

Prepare the NATless VPC Path

A private Amazon Elastic Kubernetes Service (Amazon EKS) control plane still needs private paths to AWS services. AWS Fargate Pods need those paths too.

Without NAT, Domain Name System (DNS) must resolve private service names. Two Availability Zones provide the required subnet spread.

VPC endpoints become the launch path for the AWS services your cluster needs. In this step, you'll build that path before creating the cluster.

In this step, get ready to:
  • Enable VPC DNS settings and validate two private subnets.
  • Create the endpoint security group and four interface endpoints.
  • Add the Amazon S3 gateway endpoint and verify the private routes.
Validate the VPC and private subnets

Your existing Amazon VPC needs DNS support before interface endpoints can use private service names. The cluster also needs two suitable subnets in separate Availability Zones.

  • Confirm the Region selector in the top navigation bar shows us-east-1.
  • Use the console search bar to search for VPC.
  • Select VPC from the search results.
  • Choose Your VPCs in the navigation pane.
  • Select the existing VPC that contains your private subnet.
  • Choose Actions.
  • Choose Edit VPC settings.
  • Select Enable for DNS resolution.

The DNS resolution option now shows as selected. This allows the VPC resolver to answer private DNS queries.

  • Select Enable for DNS hostnames.
  • Choose Save changes.
  • Review the VPC details to confirm DNS resolution is enabled.
  • Review the VPC details to confirm DNS hostnames are enabled.

Good progress. Your VPC can now resolve the private names used by the interface endpoints.

What Makes a Subnet Suitable?

Amazon EKS requires at least two subnets in different Availability Zones. Each selected subnet needs at least 6 available IP addresses.

AWS recommends at least 16 available IP addresses. Neither selected subnet can use Availability Zone ID use1-az3.

Check how many suitable private subnets already exist in this VPC. Choose the tab that matches what you find.

✔️ Two suitable subnets exist

  • Choose Subnets in the VPC navigation pane.
  • Filter the subnet list to your existing VPC.
  • Select the first suitable private subnet.
  • Record the first suitable subnet ID as first private subnet ID.
  • Record its IPv4 CIDR as first private subnet CIDR.
  • Confirm its Availability Zone ID is not use1-az3.
  • Confirm it has at least 6 available IPv4 addresses.

The first subnet meets the placement requirements. Use its Availability Zone as the comparison point for the second subnet.

  • Select the second suitable private subnet.
  • Record the second suitable subnet ID as second private subnet ID.
  • Record its IPv4 CIDR as second private subnet CIDR.
  • Confirm its Availability Zone differs from the first subnet.
  • Confirm its Availability Zone ID is not use1-az3.
  • Confirm it has at least 6 available IPv4 addresses.

You now have two suitable subnet IDs and their CIDR ranges ready for the endpoint configuration.

ⓧ I only have one suitable subnet

A second subnet gives the cluster the required multi-AZ placement. Create it inside the existing VPC using an unused CIDR range.

  • Choose Subnets in the VPC navigation pane.
  • Choose Create subnet.
  • Select your existing VPC under VPC ID.
  • Select an Availability Zone that differs from the existing suitable subnet.
  • Avoid the Availability Zone with ID use1-az3.
  • Select Manual input for the IPv4 CIDR block.
  • Enter an unused VPC CIDR range that leaves at least 16 available IPv4 addresses.
  • Choose Create subnet.

The new subnet now appears in the subnet list. It provides the second Availability Zone needed by the cluster.

  • Record the existing suitable subnet ID as first private subnet ID.
  • Record its IPv4 CIDR as first private subnet CIDR.
  • Record the new subnet ID as second private subnet ID.
  • Record its IPv4 CIDR as second private subnet CIDR.
  • Choose Route tables in the navigation pane.
  • Choose Create route table.
  • Select the existing VPC under VPC.
  • Choose Create route table.

The new route table starts with the local VPC route. It has no public default route.

  • Select the new route table.
  • Choose the Subnet associations tab.
  • Choose Edit subnet associations.
  • Select second private subnet ID.
  • Choose Save associations.

The second subnet now uses a private route table with no NAT gateway route or internet gateway route.

The route table attached to each subnet determines whether the subnet has a public fallback. Record both route table IDs before creating the gateway endpoint.

  • Choose Subnets in the VPC navigation pane.
  • Select first private subnet ID.
  • Choose the Route table tab.
  • Record the associated route table ID as first private route table ID.
  • Confirm the route table has no 0.0.0.0/0 route to a NAT gateway or internet gateway.

The first subnet has no public default route. Check the second subnet with the same standard.

  • Select second private subnet ID.
  • Choose the Route table tab.
  • Record the associated route table ID as second private route table ID.
  • Confirm the route table has no 0.0.0.0/0 route to a NAT gateway or internet gateway.
Create the endpoint security group and interface endpoints

A security group controls traffic into each interface endpoint. Both private subnet CIDR ranges need inbound HTTPS access on TCP port 443.

  • Choose Security groups in the VPC navigation pane.
  • Choose Create security group.
  • Enter endpoint security group name in the Security group name field.
  • Enter HTTPS access for private VPC endpoints in the Description field.
  • Select the existing VPC under VPC.

The security group form now points to the same VPC as your two private subnets.

  • Choose Add rule under Inbound rules.
  • Select HTTPS under Type.
  • Set Source to first private subnet CIDR.
  • Choose Add rule again.
  • Select HTTPS under Type.
  • Set Source to second private subnet CIDR.
  • Choose Create security group.

The endpoint security group now allows inbound TCP 443 from both private-subnet CIDR ranges.

Interface Endpoint Charges

These are the first resources in this project that add hourly charges. Interface endpoint billing applies in each selected Availability Zone.

The endpoints also incur data-processing charges. The cleanup section shows how to remove them when you finish.

The private image pull uses Amazon Elastic Container Registry (Amazon ECR) endpoints for registry traffic. Additional endpoints support cluster integration and temporary AWS credentials.

  • Choose Endpoints in the VPC navigation pane.
  • Choose Create endpoint.
  • Select AWS services under Service category.
  • Select com.amazonaws.us-east-1.ecr.api under Services.

This first endpoint provides the private Amazon ECR API path. The remaining settings connect that endpoint to your two subnets.

  • Select the existing VPC under VPC.
  • Select first private subnet ID under Subnets.
  • Select second private subnet ID under Subnets.
  • Enable Private DNS.
  • Select endpoint security group name under Security groups.
  • Choose Create endpoint.

The endpoint list now includes the ECR API interface endpoint. Its network interfaces use your two private subnets.

The next three endpoints use the same VPC settings. Keep both private subnets selected for every endpoint.

  • Create another interface endpoint with service com.amazonaws.us-east-1.ecr.dkr using the same endpoint settings.

The endpoint list now includes the ECR DKR path used for image registry traffic.

  • Create another interface endpoint with service com.amazonaws.us-east-1.ec2 using the same endpoint settings.

The endpoint list now includes the private Amazon EC2 integration path.

  • Create another interface endpoint with service com.amazonaws.us-east-1.sts using the same endpoint settings.

The endpoint list now includes the private AWS Security Token Service (AWS STS) path used by Fargate.

  • Refresh the endpoint list until all four interface endpoints report an available state.
  • Select each interface endpoint to confirm both private subnet IDs are listed.
  • Confirm private DNS is enabled for each interface endpoint.
  • Confirm the endpoint security group is attached to each interface endpoint.

Endpoint Creation Rejected?

Check that DNS resolution is enabled on the VPC. Check that DNS hostnames are enabled too.

Confirm both selected subnets belong to the existing VPC. Confirm the endpoint security group belongs to that VPC.

Ask for help with this configuration: Help me diagnose why my interface VPC endpoint cannot be created in these two private subnets.

Add the S3 gateway endpoint and verify the route

Amazon ECR stores image layers in Amazon S3. A gateway endpoint gives both subnet route tables a private path to those layers.

  • Return to Endpoints in the VPC console.
  • Choose Create endpoint.
  • Select AWS services under Service category.
  • Add the Type filter with the value Gateway.
  • Select com.amazonaws.us-east-1.s3 under Services.

The selected service is the Amazon S3 gateway endpoint for us-east-1.

  • Select the existing VPC under VPC.
  • Select first private route table ID under Route tables.
  • Select second private route table ID under Route tables.
  • Choose Create endpoint.

Why Does Amazon S3 Use a Gateway Endpoint?

The gateway endpoint adds an AWS-managed Amazon S3 prefix-list route to each selected route table. Image-layer traffic follows that route without a NAT gateway.

Amazon S3 gateway endpoints have no additional charge. The four interface endpoints keep their separate hourly and data-processing charges.

Before the final check, do you expect either private route table to show a public default route?

  • In Your VPCs, confirm DNS resolution is enabled for the existing VPC.
  • In Your VPCs, confirm DNS hostnames are enabled for the existing VPC.
  • In Subnets, confirm the two selected private subnets use different Availability Zones.
  • Confirm neither selected subnet uses Availability Zone ID use1-az3.
  • Confirm each selected subnet has at least 6 available IPv4 addresses.

The VPC foundation now passes the DNS and subnet checks. Finish by checking every endpoint path.

  • Confirm com.amazonaws.us-east-1.ecr.api is an interface endpoint in both private subnets.
  • Confirm com.amazonaws.us-east-1.ecr.dkr is an interface endpoint in both private subnets.
  • Confirm com.amazonaws.us-east-1.ec2 is an interface endpoint in both private subnets.
  • Confirm com.amazonaws.us-east-1.sts is an interface endpoint in both private subnets.
  • Confirm private DNS is enabled on all four interface endpoints.
  • Confirm the endpoint security group is attached to all four interface endpoints.
  • Confirm the endpoint security group allows inbound TCP 443 from both private-subnet CIDR ranges.

All four interface endpoints now share the required private network settings. The last check proves that Amazon S3 has a route with no public fallback.

  • Select the Amazon S3 gateway endpoint to confirm both private route table IDs are associated.
  • Inspect first private route table ID to confirm the Amazon S3 prefix-list route targets the gateway endpoint.
  • Confirm first private route table ID has no 0.0.0.0/0 route to a NAT gateway or internet gateway.
  • Inspect second private route table ID to confirm the Amazon S3 prefix-list route targets the gateway endpoint.
  • Confirm second private route table ID has no 0.0.0.0/0 route to a NAT gateway or internet gateway.

That's the private path complete. Your VPC now has four interface endpoints plus the Amazon S3 gateway endpoint with no NAT or internet gateway fallback.

Your NATless service path is ready for cluster traffic and private image pulls. Next up, you'll create the private-only Amazon EKS control plane and reach it through AWS CloudShell.

Create and Reach the Private-Only Cluster

Your NATless network path now reaches AWS services through private VPC endpoints. The next goal is to place a managed control plane inside that prepared network.

A private Amazon Elastic Kubernetes Service (Amazon EKS) control plane keeps its Kubernetes API off the public internet. You will prove that the API is reachable before any workload compute exists.

In this step, get ready to:
  • Prepare the cluster role that Amazon EKS uses to manage AWS resources.
  • Create a private-only Amazon EKS cluster across both private subnets.
  • Choose CloudShell or a private Session Manager host to reach the API and inspect the CoreDNS Pods.
Create or confirm the cluster IAM role

An AWS Identity and Access Management (IAM) role lets the managed control plane make permitted calls to AWS services. Its trust relationship identifies Amazon EKS while its managed policy grants the required cluster permissions.

✔️ I have a cluster role

  • Use the AWS Management Console search bar to select IAM.
  • Choose Roles from the left navigation pane.
  • Search for the existing Amazon EKS cluster role.
  • Select the role from the results.
  • Choose the Permissions tab.
  • Confirm that AmazonEKSClusterPolicy is attached.
  • Choose the Trust relationships tab.
  • Confirm that the trusted service principal is eks.amazonaws.com.

ⓧ I need to create the role

  • Use the AWS Management Console search bar to select IAM.
  • Choose Roles from the left navigation pane.
  • Choose Create role.
  • Select AWS service under Trusted entity type.
  • Choose EKS under Use cases for other AWS services.
  • Choose EKS - Cluster for the use case.
  • Choose Next.

What does this use case configure?

The EKS cluster use case sets eks.amazonaws.com as the trusted service principal. It also prepares AmazonEKSClusterPolicy for the role.

  • Choose Next on the Add permissions page.
  • Enter eksClusterRole in the Role name field.
  • Choose Create role.
  • Select eksClusterRole from the role list.
  • Choose the Permissions tab.
  • Confirm that AmazonEKSClusterPolicy is attached.
  • Choose the Trust relationships tab.
  • Confirm that the trusted service principal is eks.amazonaws.com.

That role now passes both checks. Amazon EKS can assume it and use the required cluster permissions.

Create the private-only cluster

Creating this cluster starts standard-support control-plane charges at $0.10 per cluster per hour. The cleanup section gives you a direct path to stop those charges when you finish.

  • Use the AWS Management Console search bar to find Amazon Elastic Kubernetes Service.
  • Select Amazon Elastic Kubernetes Service from the results.
  • Choose Clusters from the left navigation pane.
  • Choose Add cluster.
  • Choose Create.
  • Select Custom configuration.
  • Turn Use EKS Auto Mode off.
  • Enter my-cluster in the Name field.

Why use custom configuration?

Custom configuration gives you explicit control over the VPC placement and API endpoint access. Those settings are essential for keeping this cluster private-only.

  • Select your verified cluster role under Cluster IAM role.
  • Select 1.36 under Kubernetes version.
  • Select Standard support under Support type.
  • Choose Next.
  • Select the existing VPC from earlier under VPC.
  • Select the two validated private subnets under Subnets.
  • Enable Private access.
  • Disable Public access.

How does private-only access work?

Private access gives the API server an endpoint inside the cluster VPC. Disabling public access prevents requests from reaching the API through a public endpoint.

  • Keep Amazon EKS managed control-plane egress enabled.
  • Choose Next on the Specify networking page.
  • Choose Next on the Configure observability page.
  • Keep the three default add-ons selected: Amazon VPC CNI, CoreDNS, and kube-proxy.
  • Choose Next on the Select add-ons page.
  • Keep the preselected versions on the Configure selected add-ons settings page.
  • Choose Next.
  • Review the cluster configuration on the Review and create page.

What do the default add-ons provide?

Amazon VPC CNI connects Pods to VPC networking. CoreDNS provides cluster DNS while kube-proxy supports Service networking.

  • Confirm that private API access is enabled on the review page.
  • Confirm that public API access is disabled on the review page.
  • Confirm that both private subnets appear on the review page.
  • Choose Create.

Cluster provisioning takes several minutes. The console may stay on the creating state while Amazon EKS builds the control plane.

  • Wait until the cluster Status shows ACTIVE.

The private control plane is now provisioned across your two subnets. Your next check proves that you can actually reach its API.

Reach the private API and inspect CoreDNS

Use CloudShell

AWS documents the cluster Connect action as the shortest route to a private API. AWS CloudShell launches a VPC environment with kubectl ready for the cluster.

  • Choose Connect on the my-cluster details page.
  • Let AWS CloudShell launch its VPC environment.
  • Wait for the prompt to confirm that kubectl is configured for my-cluster.

Use a private EC2 host

A private Amazon EC2 host gives you a reusable administration path inside the VPC. AWS Systems Manager Session Manager opens the shell without a public IP address, an SSH key, or an inbound security-group rule.

This route adds paid resources

The t3.micro host costs about $0.0104 per hour in US East (N. Virginia). Each new interface endpoint is also billed in every selected Availability Zone, so delete this route when you finish.

Stage the kubectl binary

  • Download the Amazon EKS kubectl 1.36.2 Linux binary to your computer.
  • Download its SHA-256 checksum file to the same folder.
  • Use the AWS Management Console search bar to select S3.
  • Choose Create bucket.
  • Select us-east-1 under AWS Region.
  • Enter your globally unique kubectl staging bucket in the Bucket name field.
  • Keep Block all public access enabled.
  • Choose Create bucket.
  • Open the new bucket from the bucket list.
  • Choose Upload.
  • Choose Add files.
  • Select the downloaded kubectl file.
  • Select the downloaded kubectl.sha256 file.
  • Choose Upload.
  • Confirm that both objects appear in the bucket.

Create the host role

  • Use the AWS Management Console search bar to select IAM.
  • Choose Roles from the left navigation pane.
  • Choose Create role.
  • Select AWS service as the trusted entity type.
  • Select EC2 as the service or use case.
  • Attach AmazonSSMManagedInstanceCore on the permissions page.
  • Enter private-eks-admin-host-role in the Role name field.
  • Choose Create role.
  • Open private-eks-admin-host-role from the role list.
  • Choose Add permissions on the Permissions tab.
  • Choose Create inline policy.
  • Choose the JSON policy editor.
  • Grant the host access to describe the cluster and read the staged binary by pasting this policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "eks:DescribeCluster",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::[[KUBECTL_BUCKET_NAME="your globally unique kubectl staging bucket"]]/*"
    }
  ]
}

What does this policy allow?

  • eks:DescribeCluster lets the AWS CLI build a kubeconfig for my-cluster.
  • s3:GetObject lets the host download only objects from your staging bucket.
  • Choose Next.
  • Enter PrivateEksHostAccess in the policy-name field.
  • Choose Create policy.
  • Confirm that the managed and inline policies both appear on the role.

Add the private service endpoints

Which endpoints does the host need?

  • com.amazonaws.us-east-1.ssm reaches the Systems Manager API.
  • com.amazonaws.us-east-1.ssmmessages carries the Session Manager data channel.
  • com.amazonaws.us-east-1.ec2messages supports agent messaging in US East (N. Virginia).
  • com.amazonaws.us-east-1.eks lets the host call the EKS API without NAT. The cluster's private Kubernetes endpoint remains separate.
  • Use the AWS Management Console search bar to select VPC.
  • Choose Endpoints from the left navigation pane.
  • Choose Create endpoint.
  • Create the com.amazonaws.us-east-1.ssm interface endpoint in both private subnets.
  • Create the com.amazonaws.us-east-1.ssmmessages interface endpoint in both private subnets.
  • Create the com.amazonaws.us-east-1.ec2messages interface endpoint in both private subnets.
  • Create the com.amazonaws.us-east-1.eks interface endpoint in both private subnets.
  • Enable Private DNS names on every endpoint.
  • Attach the existing endpoint security group to every new interface endpoint.
  • Wait until all four endpoint states show Available.

Launch the private host

  • Use the AWS Management Console search bar to select EC2.
  • Choose Instances from the left navigation pane.
  • Choose Launch instances.
  • Enter private-eks-admin-host in the Name field.
  • Select the latest standard Amazon Linux 2023 AMI.
  • Select t3.micro under Instance type.
  • Choose Proceed without a key pair under Key pair.
  • Choose Edit under Network settings.
  • Select the cluster VPC under VPC.
  • Select either validated private subnet under Subnet.
  • Disable Auto-assign public IP.
  • Create a security group named private-eks-admin-host-sg with no inbound rules.
  • Keep the default outbound rule because the subnet has no internet route.
  • Expand Advanced details.
  • Select private-eks-admin-host-role under IAM instance profile.
  • Choose Launch instance.

The host normally appears in Systems Manager within a few minutes. That wait reflects the SSM Agent registering through your new endpoints.

Authorize the host for the cluster

  • Open my-cluster in the Amazon EKS console.
  • Choose the Networking tab.
  • Open the linked cluster security group.
  • Choose Edit inbound rules.
  • Add an HTTPS rule with private-eks-admin-host-sg as its source.
  • Choose Save rules.
  • Return to my-cluster.
  • Choose the Access tab.
  • Choose Create access entry.
  • Select the ARN for private-eks-admin-host-role as the principal.
  • Choose Next.
  • Associate AmazonEKSClusterAdminPolicy.
  • Select Cluster as the access scope.
  • Choose Add policy.
  • Choose Create.

Open the session and install kubectl

  • Open private-eks-admin-host in the Amazon EC2 console.
  • Choose Connect.
  • Choose the Session Manager tab.
  • Choose Connect.
  • Download and verify the staged client inside the session by running:
aws s3 cp s3://[[KUBECTL_BUCKET_NAME="your globally unique kubectl staging bucket"]]/kubectl .
aws s3 cp s3://[[KUBECTL_BUCKET_NAME="your globally unique kubectl staging bucket"]]/kubectl.sha256 .
sha256sum -c kubectl.sha256
chmod +x kubectl
sudo mv kubectl /usr/local/bin/kubectl
kubectl version --client

You should see kubectl: OK followed by a client version in the 1.36 release line.

  • Configure the host for the private cluster by running this command:
aws eks update-kubeconfig --region us-east-1 --name my-cluster

You should see confirmation that the my-cluster context was added to ~/.kube/config.

Can't open the private session?

  • Confirm that the four new interface endpoints show Available.
  • Confirm that their security group allows inbound HTTPS from the private subnet CIDRs.
  • Confirm that AmazonSSMManagedInstanceCore remains attached to the host role.

Ask for help with the registration state you see: Why is my private host unavailable in Session Manager?

Before you run these checks, do you think a reachable control plane means its system Pods can run too?

  • Test API reachability plus system Pod state by running:
kubectl get svc
kubectl get pods -n kube-system

What do these checks test?

  • The first command asks the private Kubernetes API for its Services.
  • The second command inspects system Pods in the kube-system namespace.

You will see the built-in kubernetes ClusterIP Service in the first output. The CoreDNS Pods show Pending in the second output.

You have proved that the private API path works. The Pending state is the intended shortfall because the cluster has no compute profile for those Pods.

Seeing unexpected output?

  • Confirm that the cluster status still shows ACTIVE.
  • Reopen the CloudShell VPC environment from the cluster's Connect action if that route cannot reach the API.
  • Confirm that the cluster security group allows HTTPS from private-eks-admin-host-sg if the Session Manager route times out.
  • Confirm that the host role has an EKS access entry with AmazonEKSClusterAdminPolicy if kubectl reports an authorization error.

Ask for help with the exact route and output you see: Help me diagnose my private EKS connection or CoreDNS status.

Your private cluster is reachable and its compute gap is visible. Next, you will give CoreDNS the AWS Fargate runtime it needs.

Give CoreDNS Fargate Compute

Your private Amazon Elastic Kubernetes Service (Amazon EKS) control plane is reachable. The CoreDNS Pods remain Pending because the cluster has no compute path for system Pods.

An AWS Fargate profile supplies compute only to Pods that match its selector. You will pair that profile with a scoped execution role.

In this step, get ready to:
  • Create the Fargate Pod execution role with permissions scoped to this cluster.
  • Create a CoreDNS Fargate profile for the existing system Pods.
  • Move CoreDNS from Pending to Running on AWS Fargate.
Create the Pod execution role

A Pod execution role gives Amazon EKS components on Fargate permission to pull images from Amazon Elastic Container Registry (Amazon ECR). A scoped AWS Identity and Access Management (IAM) trust policy confines role assumption to this cluster's Fargate profiles.

  • Open the IAM console.
  • Choose Roles in the left navigation pane.
  • Choose Create role.
  • Choose AWS service under Trusted entity type.
  • Choose EKS from Use cases for other AWS services.
  • Choose EKS - Fargate Pod.

What Does This Use Case Configure?

The EKS - Fargate Pod use case prepares the eks-fargate-pods.amazonaws.com service principal. It also selects the required AmazonEKSFargatePodExecutionRolePolicy managed policy.

  • Choose Next on the trusted entity page.
  • Choose Next on the permissions page.
  • Enter AmazonEKSFargatePodExecutionRole in the Role name field.
  • Choose Create role.
  • Choose AmazonEKSFargatePodExecutionRole from the Roles page.
  • Choose the Permissions tab.

You should see AmazonEKSFargatePodExecutionRolePolicy attached to the role.

Editing an IAM trust policy can feel high stakes. This policy narrows role assumption to your cluster's Fargate profile ARN pattern.

  • Switch back to the EKS cluster details page from earlier.
  • Record the cluster name as your cluster name.
  • Use the account menu in the console header to locate your 12-digit account ID.
  • Record the account ID as 123456789012.
  • Return to the AmazonEKSFargatePodExecutionRole page from earlier.
  • Choose the Trust relationships tab.
  • Choose Edit trust policy.
  • Replace the policy contents with this cluster-scoped trust policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Condition": {
        "ArnLike": {
          "aws:SourceArn": "arn:aws:eks:us-east-1:[[AWS_ACCOUNT_ID="123456789012"]]:fargateprofile/[[EKS_CLUSTER_NAME="your cluster name"]]/*"
        }
      },
      "Principal": {
        "Service": "eks-fargate-pods.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

What Does This Trust Policy Do?

  • The Principal permits Amazon EKS Fargate Pods to request the role.
  • The aws:SourceArn condition restricts requests to Fargate profiles under your cluster.
  • The final wildcard covers every Fargate profile name under that cluster.
  • Choose Update policy.
  • Choose the Permissions tab.

You should still see AmazonEKSFargatePodExecutionRolePolicy attached.

  • Choose the Trust relationships tab.

You should see a source ARN that names your account in us-east-1. The ARN should also name your cluster before the final wildcard.

Can't Confirm the Role?

If the managed policy is missing, confirm that you selected the EKS Fargate Pod use case. If the role cannot be selected later, confirm that its trust principal is eks-fargate-pods.amazonaws.com.

Check that the source ARN contains your 12-digit account ID plus the exact cluster name. Help me troubleshoot my Fargate Pod execution role.

Create the CoreDNS Fargate profile

A Fargate profile uses a namespace plus optional Kubernetes labels to decide which Pods receive Fargate compute. CoreDNS needs both its system namespace and its DNS label to match.

Why AWS Fargate Here?

A managed Amazon EC2 node group introduces instance sizing plus node lifecycle work. Those tasks do not advance this project's private image-pull goal.

AWS Fargate supplies compute per matching Pod. This keeps your attention on private networking plus IAM selectors.

  • Switch back to the EKS cluster details page from earlier.
  • Choose Compute in the left navigation pane.
  • Choose the Fargate profiles tab.
  • Choose Add Fargate profile.
  • Enter coredns in the Name field.
  • Choose AmazonEKSFargatePodExecutionRole for Pod execution role.
  • Confirm both private subnets from the cluster are selected.
  • Choose Next.

How Does the Selector Work?

The namespace limits this profile to system Pods in kube-system. The label narrows that match to Pods carrying k8s-app=kube-dns.

  • Enter kube-system in the Namespace field.
  • Add a Kubernetes label to the selector.
  • Enter k8s-app as the label key.
  • Enter kube-dns as the label value.
  • Choose Next.
  • Confirm the review page lists the coredns profile with both private subnets.

Profile creation may take a few minutes while Amazon EKS prepares the scheduling path. A short wait here is expected.

  • Choose Create.
  • Wait until the Fargate profile status shows Active.

You have closed the compute gap for CoreDNS. New Pods matching both selector fields can now use Fargate.

Can't Create the Profile?

If the execution role is absent from the role list, check its trust principal. If a subnet is unavailable, confirm that it has no direct route to an internet gateway.

Confirm the profile uses both private subnets plus the exact selector values from this step. Help me troubleshoot my CoreDNS Fargate profile.

Restart CoreDNS on Fargate

The existing Pending Pods were created before the matching profile existed. Restarting the CoreDNS Deployment creates replacements that the Fargate scheduler can match.

Fargate Compute Is Billed

CoreDNS starts using metered Fargate compute when the replacement Pods launch. The cleanup section gives you options for removing the project's billable resources.

  • Return to the connected shell you chose in Step 2.
  • Restart the CoreDNS Deployment by running:
kubectl rollout restart -n kube-system deployment coredns

What Does This Command Do?

  • The rollout restart replaces the existing CoreDNS Pods.
  • The new Pods pass through scheduling again with the active Fargate profile available.

Before you check, do you expect the replacement Pods to stay Pending or reach Running now that their selector has compute?

  • Check the CoreDNS Pod status by running:
kubectl get pods -n kube-system

What Should You See?

You should see the CoreDNS Pods move to Running. Every CoreDNS row must reach that status to confirm the Fargate compute path works.

A first check can catch a replacement Pod during startup. The next check should show its progress.

  • Repeat the status check until every CoreDNS Pod shows Running.

CoreDNS Still Pending?

Confirm the coredns profile is active. Check that its namespace is kube-system plus its label selector is k8s-app=kube-dns.

Confirm the execution role still has its managed policy plus the cluster-scoped trust relationship. Help me diagnose Pending CoreDNS Pods on Fargate.

That is the compute gap resolved. CoreDNS is now Running on AWS Fargate inside your private-only cluster.

Your cluster now has working system DNS without an EC2 node group. Next up, you will give your application namespace its own Fargate compute path.

Deploy the Private ECR Image

CoreDNS is now Running on AWS Fargate. Your private-only Amazon EKS cluster finally has working system DNS.

Application Pods only receive Fargate compute when their namespace matches a Fargate profile. You will create that profile before deploying your private Amazon ECR image from the shell path you chose in Step 2.

In this step, get ready to:
  • Create a Fargate profile for the application namespace.
  • Create the namespace and Deployment manifests.
  • Apply the manifests and verify the successful rollout.
Create the application Fargate profile

A Fargate profile uses selectors to decide which Pods receive Fargate compute. A namespace-only selector covers every Pod created inside that namespace.

  • Switch back to the Amazon EKS console tab from earlier.
  • Return to the details page for your private cluster.
  • Choose Compute in the left navigation pane.
  • Choose the Fargate profiles tab.
  • Choose Add Fargate profile.

What does the profile control?

The profile connects a Pod selector to private subnets plus a Pod execution role. Matching Pods can then start on Fargate without an EC2 node group.

  • Enter private-app in the Name field.
  • Choose AmazonEKSFargatePodExecutionRole for Pod execution role.
  • Select both private subnets under Subnets.
  • Choose Next.

The first page now connects the profile to the same private network path used by CoreDNS. The next page defines which application Pods match it.

  • Enter private-app in the Namespace field.
  • Leave the optional Kubernetes labels empty.
  • Choose Next.
  • Confirm the review page lists both private subnets plus AmazonEKSFargatePodExecutionRole.
  • Choose Create.
  • Wait until the profile finishes creating.
  • Confirm the profile details show the private-app namespace without a label selector.

That is the application compute path ready. Every Pod in the private-app namespace can now match this profile.

Profile not created?

Confirm that you selected AmazonEKSFargatePodExecutionRole. Check that both selected subnets are the private subnets used by the cluster.

Ask for help with the profile configuration.

Create the namespace and Deployment manifests

The YAML manifests define the selector boundary plus the workload that enters it. The Namespace must exist before the Deployment can create its Pod.

  • Return to the connected shell you chose in Step 2.
  • Start writing namespace.yaml by running this command:
cat > namespace.yaml

What does this command do?

The cat command sends everything you paste into namespace.yaml. Pressing Ctrl+D closes the input and saves the file.

Your shell is now waiting for the manifest content.

  • Approve a Safe Paste warning if your shell displays one after the next action.
  • Paste this manifest into the waiting terminal:
apiVersion: v1
kind: Namespace
metadata:
  name: private-app

What does this manifest do?

  • The Namespace kind creates an isolation boundary inside the cluster.
  • The private-app name matches the namespace selector in your new Fargate profile.
  • Press Ctrl+D to save namespace.yaml.
  • Confirm the file exists by running this command:
ls

What should I see?

The directory listing includes namespace.yaml. This confirms that the manifest was saved in your chosen shell environment.

Namespace file missing?

Run pwd to confirm which directory your shell is using. Start the cat command again if you closed the session before pressing Ctrl+D.

Help me find my saved manifest.

The second manifest describes one application replica. Its labels connect the Deployment selector to the Pod template.

  • Start writing deployment.yaml by running this command:
cat > deployment.yaml

What does this command do?

The shell sends the manifest you paste into deployment.yaml. Pressing Ctrl+D closes the input and saves the file.

Your shell is now waiting for the Deployment manifest.

  • Enter your private ECR image URI and tag in the highlighted image field below.
  • Approve a Safe Paste warning if your shell displays one after the next action.
  • Paste the completed manifest into the waiting terminal:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: private-app
  namespace: private-app
  labels:
    app: private-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: private-app
  template:
    metadata:
      labels:
        app: private-app
    spec:
      containers:
        - name: private-app
          image: [[ECR_IMAGE_URI="your private ECR image URI and tag"]]

What does this manifest do?

  • The replicas: 1 setting asks Kubernetes to keep one application Pod running.
  • The app: private-app label connects the Deployment selector to its Pod template.
  • The container entry tells Fargate which private ECR image to pull.
  • Press Ctrl+D to save deployment.yaml.

The saved image line now identifies your private Linux amd64 image in us-east-1.

  • Confirm both manifest files exist by running this command:
ls

What should I see?

The directory listing includes namespace.yaml plus deployment.yaml. Both files are ready to apply.

Deployment file missing?

Run pwd to confirm your current directory. Start the cat command again if you closed the shell before pressing Ctrl+D.

Help me check my manifest files.

✔️ Awesome, I've got everything!

Great. Double check that both manifest files are saved.

Your saved deployment.yaml uses the private ECR image URI and tag you entered.

ⓧ I'd like to double check the full code

Here is the complete namespace.yaml template:

apiVersion: v1
kind: Namespace
metadata:
  name: private-app

Here is the complete deployment.yaml template. Your saved image line contains your exact private ECR URI plus tag.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: private-app
  namespace: private-app
  labels:
    app: private-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: private-app
  template:
    metadata:
      labels:
        app: private-app
    spec:
      containers:
        - name: private-app
          image: [[ECR_IMAGE_URI="your private ECR image URI and tag"]]
Apply the manifests and verify the rollout

Applying the Namespace first creates the selector boundary expected by the Fargate profile. Applying the Deployment next creates the Pod inside that boundary.

  • Create the application namespace by running this command:
kubectl apply -f namespace.yaml

What does this command do?

The command sends namespace.yaml to the Kubernetes API. Kubernetes creates the private-app namespace if it does not exist.

You will see a response confirming that the namespace was created.

Namespace not created?

Confirm that the command uses namespace.yaml exactly. Check that the file contains name: private-app.

Help me apply the namespace manifest.

  • Create the application Deployment by running this command:
kubectl apply -f deployment.yaml

What does this command do?

The command creates the private-app Deployment inside the matching namespace. EKS schedules its Pod through the private-app Fargate profile.

You will see a response confirming that the Deployment was created.

Deployment not created?

Confirm that deployment.yaml still uses the private-app namespace. Check that its image line contains your complete private ECR URI plus tag.

Help me apply the Deployment manifest.

Before you check the rollout, do you expect the Pod to reach Running now that its namespace matches a Fargate profile?

  • Verify the application rollout by running this command:
kubectl rollout status deployment/private-app -n private-app

What does this command check?

The rollout check waits for the Deployment to reach its desired state. Success proves that the one-replica workload started through the matching Fargate profile.

The command reports that the Deployment successfully rolled out. Your private ECR image is now running as one Fargate Pod.

Rollout still waiting?

Check that the private-app Fargate profile uses both private subnets. Confirm that its namespace selector is private-app.

Verify that the image line uses the exact private ECR repository URI plus tag. A missing image or mismatched tag prevents the container from starting.

Help me diagnose the stalled rollout.

Your private image is running on Fargate. Next up, you will inspect the Pod plus its network path to prove the image pull stayed private.

Prove the Pull Stayed Private

Your private-app Pod is now Running on AWS Fargate. That result proves Amazon EKS found compute for the workload.

A Running state still leaves one question: did Amazon ECR deliver the image through the intended private path? You will inspect the live Pod evidence to answer it.

You will pair that evidence with the existing Amazon VPC configuration. The VPC endpoints and private route tables rule out a public fallback path.

In this step, get ready to:
  • Capture the Pod's private placement details.
  • Confirm the image-pull evidence.
  • Verify the NATless endpoint path.
Inspect the Pod's private placement

The wide Pod view in your chosen administration shell reveals where the workload is running. It includes the private address assigned inside your VPC.

Before you run this check, which details do you expect to connect the Pod to Fargate and the private network?

  • Display the Pod's placement details by running this command:
kubectl get pods -n private-app -o wide

What does this output prove?

  • The wide view adds the Pod address and assigned node to the normal status output.
  • A private VPC address ties the Pod to your private subnet path.
  • The assigned Fargate node proves the workload does not depend on an EC2 worker-node group.
  • Record the Pod's private IP here: your Pod private IP.
  • Record the Fargate node name here: your Fargate node name.
  • Record the Pod state here: your Pod state.

Solid progress. You now have direct evidence that one application Pod is Running on Fargate with a private VPC address.

Pod details missing?

  • Confirm the command includes -n private-app so it reads the application namespace.
  • Confirm the private-app Fargate profile selects the private-app namespace.

Ask for help with the placement evidence: Help me diagnose my private-app Pod placement output.

Trace the image-pull evidence

The Pod description connects the running container to its exact image. It also preserves the event trail from scheduling through image startup.

Before you inspect it, which fields do you think provide the strongest proof that the private ECR image started cleanly?

  • Describe the labeled application Pod by running this command:
kubectl describe pods -n private-app -l app=private-app

How does the description help?

  • The Image value shows the private ECR URI and tag requested by the Deployment.
  • The Image ID identifies the image that the container runtime resolved.
  • The State value reports whether the container is running.
  • The Events section records scheduling and image-pull activity.
  • Compare the Image value with the exact private ECR URI and tag that you applied.
  • Confirm the Image ID identifies the resolved image.
  • Confirm the container State shows Running.
  • Scan Events for any image-pull failure.

You should see your exact private ECR URI in Image. The Image ID should identify the pulled image.

The container State should be Running. The event list should contain no image-pull failure.

Good evidence. The live container now ties your successful rollout to the intended private ECR image.

Seeing an image-pull problem?

  • Use the newest reason in Events as your starting point.
  • Compare the image reference with the exact private ECR URI and tag you applied.
  • Recheck the endpoint for any AWS service named by the event details.

Ask for help interpreting the evidence: Help me diagnose this image-pull event.

This investigation reads the live cluster state. Your two manifest files remain the deployment source.

✔️ Awesome, I've got everything!

Your namespace.yaml and deployment.yaml remain unchanged. The live Pod evidence confirms that these manifests produced the expected workload.

ⓧ I'd like to double check the full code

  • Compare namespace.yaml with this reference:
apiVersion: v1
kind: Namespace
metadata:
  name: private-app

This manifest defines the private-app namespace selected by the application Fargate profile.

  • Compare deployment.yaml with this reference:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: private-app
  namespace: private-app
  labels:
    app: private-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: private-app
  template:
    metadata:
      labels:
        app: private-app
    spec:
      containers:
        - name: private-app
          image: [[ECR_IMAGE_URI="your private ECR image URI and tag"]]

This manifest defines one labeled application replica. Its container image field holds the private ECR reference used for the deployment.

Recheck the NATless network path

A private endpoint path works only when every required AWS service is covered. The endpoint configuration provides that service access.

The private-subnet route tables rule out NAT or internet-gateway fallback. The endpoint security group permits private HTTPS traffic.

Before this final check, do you expect to find any route that could carry the image pull onto the public internet?

  • Switch back to the Amazon VPC console from earlier.
  • Select Endpoints in the left navigation.
  • Confirm the ECR API, ECR DKR, EC2, and STS interface endpoints are listed for your VPC.
  • Confirm each interface endpoint uses both private subnets.
  • Confirm private DNS is enabled for each interface endpoint.
  • Follow the security group link from one interface endpoint.
  • Confirm the endpoint security group allows inbound HTTPS on TCP 443 from the private subnet CIDRs.

Why does the security group matter?

The interface endpoints use private network interfaces inside your VPC. Their security group must accept HTTPS traffic from the private subnets before Fargate can reach the AWS service APIs.

  • Return to the first private subnet's route table from earlier.
  • Confirm the S3 prefix-list route targets the S3 gateway endpoint.
  • Repeat the S3 route check for the second private subnet's route table.
  • Confirm neither private-subnet route table has a default route to a NAT gateway.
  • Confirm neither private-subnet route table has a default route to an internet gateway.

Your recorded Pod is Running on Fargate with a private VPC address. Its described image matches the private ECR reference.

The four interface endpoints and the S3 gateway route are present. Neither private-subnet route table provides a public fallback path.

That closes the proof. Your image reached a Fargate Pod through the NATless private architecture you built.

Secret mission

Scale Without Adding Nodes

Your private image already runs as one Fargate Pod. Scale the existing Deployment to two Pods. Compare their private identities before returning to one replica without adding nodes.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep the project running, pause the application workload, or delete the project-created resources. The cluster, interface endpoints, and optional private EC2 host continue to generate AWS charges while they remain active.

Cost warning

A standard-support Amazon EKS cluster costs $0.10 per cluster-hour.

Your interface VPC endpoints incur hourly charges in every selected Availability Zone. Data-processing charges also apply.

AWS Fargate charges continue while Pods are running. Scaling the application to zero stops its compute usage, but CoreDNS, the cluster, and the endpoints continue billing.

The optional Amazon EC2 administration host adds instance charges until you stop or terminate it. Delete the host when you no longer need the Session Manager route.

Resources you used:

  • One private-only Amazon EKS cluster.
  • Two AWS Fargate profiles named coredns and private-app.
  • One Fargate Pod execution role named AmazonEKSFargatePodExecutionRole.
  • One cluster IAM role if you created it for this project.
  • Four image-pull interface endpoints and one Amazon S3 gateway endpoint.
  • One endpoint security group.
  • One Kubernetes Namespace and one Deployment named private-app.
  • Two shell-local manifests named namespace.yaml and deployment.yaml.
  • One optional private-eks-admin-host EC2 instance and its security group.
  • One optional IAM role named private-eks-admin-host-role and its EKS access entry.
  • Four optional administration interface endpoints for EKS and Systems Manager.
  • One optional Amazon S3 staging bucket containing the kubectl binary and checksum.

Keep everything running

No action is needed. Choose this if you are still testing the complete private deployment and accept the ongoing AWS charges.

  • Keep the private-app Deployment at one replica.
  • Retain both Fargate profiles.
  • Retain the cluster and all project-created VPC endpoints.
  • Retain the optional EC2 administration host only if you still use the Session Manager route.
  • Budget for the ongoing cluster, Fargate, endpoint, and optional instance charges.

Pause - I'll come back to this later

This option stops the application workload while preserving the cluster configuration. Amazon EKS, CoreDNS, interface endpoint, and optional stopped-instance storage charges continue during the pause.

  • Return to the connected shell you chose in Step 2.
  • Scale the application Deployment to zero replicas by running this command:
kubectl scale deployment/private-app --replicas=0 -n private-app

You should see confirmation that the Deployment was scaled.

  • Confirm that no application Pods remain by running this command:
kubectl get pods -n private-app -o wide

You should see no Running application Pods. This confirms that application compute has stopped.

  • Open private-eks-admin-host in the Amazon EC2 console if you created the optional host.
  • Choose Instance state.
  • Choose Stop instance.
  • Confirm that its state reaches Stopped.
  • Scale the Deployment back to one replica when you return.
  • Start the optional EC2 host before using its Session Manager route again.

Delete - I don't want to use this again

Deleting this stack is a big step. Your pre-existing VPC and private Amazon ECR repository remain available.

Delete the workload while your chosen administration shell can still reach the cluster.

  • Return to the connected shell you chose in Step 2.
  • Delete the application Deployment by running this command:
kubectl delete deployment private-app -n private-app

You should see confirmation that the Deployment was deleted.

  • Delete the application Namespace by running this command:
kubectl delete namespace private-app

You should see confirmation that the Namespace was deleted.

  • Remove the shell-local manifest files by running this command:
rm -f namespace.yaml deployment.yaml

This removes the manifest copies from your current shell. Terminating the optional EC2 host also removes files stored on its root volume.

Delete the Fargate profiles
  • Open my-cluster in the Amazon EKS console.
  • Choose Compute.
  • Choose the Fargate profiles tab.
  • Select private-app.
  • Choose Delete.
  • Wait until private-app disappears from the profile list.
  • Select coredns.
  • Choose Delete.
  • Wait until coredns disappears from the profile list.
Remove the optional administration route
  • Choose the Access tab for my-cluster if you created the private EC2 route.
  • Select the access entry for private-eks-admin-host-role.
  • Choose Delete.
  • Confirm that the access entry no longer appears.
  • Open private-eks-admin-host in the Amazon EC2 console.
  • Choose Instance state.
  • Choose Terminate instance.
  • Wait until the instance state shows Terminated.
  • Choose Security Groups in the Amazon EC2 console.
  • Select private-eks-admin-host-sg.
  • Choose Delete security groups.
  • Confirm that the host security group no longer appears.
Delete the EKS cluster
  • Return to the Amazon EKS cluster overview.
  • Select my-cluster.
  • Choose Delete.
  • Wait until my-cluster no longer appears.
Remove the VPC endpoints
  • Open the Amazon VPC console.
  • Choose Endpoints from the left navigation pane.
  • Select the four image-pull interface endpoints created for this project.
  • Select the Amazon S3 gateway endpoint created for this project.
  • Select the optional EKS, SSM, SSM Messages, and EC2 Messages interface endpoints if you created the private EC2 route.
  • Choose Actions.
  • Choose Delete VPC endpoints.
  • Wait until every selected project endpoint no longer appears.
Remove the endpoint security group
  • Choose Security groups in the Amazon VPC console.
  • Select the endpoint security group created for this project.
  • Choose Actions.
  • Choose Delete security groups.
  • Confirm that the endpoint security group no longer appears.
Delete the optional staging bucket
  • Open the Amazon S3 console if you created the private EC2 route.
  • Select your globally unique kubectl staging bucket from the bucket list.
  • Choose Empty.
  • Enter permanently delete in the confirmation field.
  • Choose Empty.
  • Return to the bucket list.
  • Select your globally unique kubectl staging bucket.
  • Choose Delete.
  • Enter the bucket name in the confirmation field.
  • Choose Delete bucket.
  • Confirm that the staging bucket no longer appears.
Remove the project IAM roles
  • Open the IAM console.
  • Choose Roles from the left navigation pane.
  • Select AmazonEKSFargatePodExecutionRole.
  • Choose Delete.
  • Select the cluster IAM role only if you created it for this project.
  • Choose Delete for that cluster role.
  • Select private-eks-admin-host-role if you created the private EC2 route.
  • Choose Delete for the host role.

That clears the paid project stack. Your original VPC and private ECR image remain available for future projects.

Nice Work!

Nice Work!

You made it! Your private-only Amazon EKS cluster now runs an image from private Amazon ECR on AWS Fargate without a NAT gateway or public Kubernetes API endpoint.

You've learned how to:

  • Create a private-only Amazon EKS control plane in an existing VPC while keeping public API access disabled.
  • Reach the private Kubernetes API through CloudShell or an optional Session Manager host with no public IP address.
  • Run a private ECR image on AWS Fargate through NATless VPC endpoints, then prove the result with a private Pod address and clean image-pull events.
  • Complete the Secret Mission by scaling to two concurrent Fargate Pods, comparing their private placement, and returning to one replica.

Ready to quiz yourself?