Amazon EKS — Deploying Applications
Deploying to EKS follows standard Kubernetes patterns with a few AWS-specific considerations: pulling images from ECR, setting resource requests for accurate scheduling, and configuring health checks for load balancer integration.
Pulling Images from ECR
Authenticate Docker to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
Push an Image
docker tag my-app:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.0
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.0
Image Pull on EKS
EKS managed nodes have the AmazonEC2ContainerRegistryReadOnly policy attached — they can pull from ECR in the same account without any additional configuration.
For cross-account ECR pull or ECR in another region, create an image pull secret:
kubectl create secret docker-registry ecr-secret \
--docker-server=987654321.dkr.ecr.us-west-2.amazonaws.com \
--docker-username=AWS \
--docker-password=$(aws ecr get-login-password --region us-west-2)
Reference in your pod spec:
spec:
imagePullSecrets:
- name: ecr-secret
Basic Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-api
namespace: production
labels:
app: web-api
version: v1
spec:
replicas: 3
selector:
matchLabels:
app: web-api
template:
metadata:
labels:
app: web-api
version: v1
spec:
containers:
- name: api
image: 123456789.dkr.ecr.us-east-1.amazonaws.com/web-api:v1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
- name: APP_ENV
value: "production"
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
kubectl apply -f web-api-deployment.yaml
kubectl rollout status deployment/web-api -n production
Resource Requests and Limits
Setting resources correctly is critical on EKS:
| Setting | Purpose | Impact |
|---|---|---|
| requests.cpu | Guaranteed CPU | Used by scheduler for node placement |
| requests.memory | Guaranteed memory | Used by scheduler; OOMKilled if exceeded (with limits) |
| limits.cpu | Maximum CPU | Pod is throttled beyond this |
| limits.memory | Maximum memory | Pod is OOMKilled if it exceeds this |
Best Practices for Resources
resources:
requests:
cpu: "250m" # Start conservative, tune with VPA
memory: "256Mi" # Set based on observed usage
limits:
memory: "512Mi" # Always set memory limits (OOMKill protection)
# cpu limits: optional — many teams skip CPU limits to avoid throttling
Tip: Set memory limits. CPU limits are debatable — throttling hurts latency-sensitive apps. Many production clusters use requests only for CPU.
Health Checks
Liveness Probe
Restarts the container if it becomes unhealthy:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
Readiness Probe
Removes the pod from service endpoints until it’s ready:
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
Startup Probe
For slow-starting applications (prevents liveness probe from killing during startup):
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
# App has 300 seconds (30 × 10) to start
EKS-Specific: Target Group Health Checks
When using the AWS Load Balancer Controller, readiness gates ensure ALB health checks pass before the pod receives traffic:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-api
spec:
template:
spec:
readinessGates:
- conditionType: target-health.alb.ingress.k8s.aws
Rolling Updates and Rollbacks
Update Strategy
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 1 extra pod during update
maxUnavailable: 0 # Never reduce below desired count
Perform an Update
# Update the image
kubectl set image deployment/web-api api=123456789.dkr.ecr.us-east-1.amazonaws.com/web-api:v2.0 -n production
# Watch the rollout
kubectl rollout status deployment/web-api -n production
Rollback
# Rollback to the previous version
kubectl rollout undo deployment/web-api -n production
# Rollback to a specific revision
kubectl rollout history deployment/web-api -n production
kubectl rollout undo deployment/web-api --to-revision=2 -n production
Exposing Your Application
ClusterIP Service (Internal)
apiVersion: v1
kind: Service
metadata:
name: web-api
namespace: production
spec:
type: ClusterIP
selector:
app: web-api
ports:
- port: 80
targetPort: 8080
NodePort Service
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 30080
LoadBalancer Service (NLB)
apiVersion: v1
kind: Service
metadata:
name: web-api
namespace: production
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
type: LoadBalancer
selector:
app: web-api
ports:
- port: 80
targetPort: 8080
For ALB Ingress, see Load Balancing.
Pod Disruption Budgets
Protect your application during node drains and cluster upgrades:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-api-pdb
namespace: production
spec:
minAvailable: 2 # At least 2 pods must be running at all times
selector:
matchLabels:
app: web-api
Or use maxUnavailable:
spec:
maxUnavailable: 1 # At most 1 pod can be disrupted at a time
PDBs are essential for managed node group rolling updates — EKS respects PDBs when draining nodes.
Best Practices
- Always set resource requests — the scheduler needs them for accurate node placement
- Set memory limits — prevent runaway containers from OOMKilling the node
- Configure readiness probes — pods shouldn’t receive traffic until they’re ready
- Use startup probes for slow-starting apps (Java apps with long startup times)
- Create PodDisruptionBudgets for every production Deployment
- Use ECR for container images — lowest latency pull from within AWS
- Tag images with versions — never deploy
:latestin production - Set
imagePullPolicy: IfNotPresentfor tagged images to speed up deployments