Amazon EKS — Deploying Applications

3 minute read

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

  1. Always set resource requests — the scheduler needs them for accurate node placement
  2. Set memory limits — prevent runaway containers from OOMKilling the node
  3. Configure readiness probes — pods shouldn’t receive traffic until they’re ready
  4. Use startup probes for slow-starting apps (Java apps with long startup times)
  5. Create PodDisruptionBudgets for every production Deployment
  6. Use ECR for container images — lowest latency pull from within AWS
  7. Tag images with versions — never deploy :latest in production
  8. Set imagePullPolicy: IfNotPresent for tagged images to speed up deployments

Updated: