Key Takeaways
- Your Dockerfile needs a non-root user and locked-down Python dependencies to cut down on security risks.
- Use `eksctl` to spin up an AWS EKS cluster with at least two worker nodes in different Availability Zones so it doesn’t fall over.
- You’ll need an `Ingress` controller (NGINX is a solid choice) in your EKS cluster to route outside traffic to your Python app.
- Define Kubernetes `Deployment` and `Service` objects for your Python app, and don’t forget resource limits and readiness probes to keep it stable.
- Set up a pipeline with AWS CodeBuild and CodePipeline to automatically build your Docker images and update EKS when you push code.
Running Python apps in Docker containers on AWS EKS is how you build for scale and resilience. By 2026, managing a bunch of microservices demands serious infrastructure, and AWS Elastic Kubernetes Service (EKS) gives you that by efficiently orchestrating your container workloads. This whole setup solves a lot of common headaches, from “it works on my machine” issues to scaling problems. But are you actually ready to use it correctly?
1. Prepare Your Python Application for Containerization
Before you deploy any Python app to Kubernetes, you have to package it correctly. That means creating a Dockerfile that spells out the app’s environment and all its dependencies. For a standard Flask or FastAPI app, your Dockerfile ought to start with a specific Python base image. I usually go with an official slim image like python:3.10-slim-bullseye because it keeps the final image from getting bloated. Bigger images mean slower deployments and higher storage bills.
Here’s a standard Dockerfile to get you started:
FROM python:3.10-slim-bullseye
WORKDIR /app
COPY requirements.txt .
RUN pip install, no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", ", bind", "0.0.0.0:8000", "app:app"]
This assumes you have a requirements.txt file listing your Python packages and your main file is app.py with a Flask/FastAPI instance named app. I find gunicorn to be a reliable, production-ready WSGI server that handles concurrent requests way better than any development server. Make sure you specify the exact Python version for consistency. If your local dev setup uses Python 3.10.12, your Dockerfile needs to match that specific patch version to avoid surprises.
Pro Tip: Optimize Docker Image Size and Security
To really shrink your Docker image, use multi-stage builds. That lets you separate the dependencies you need to build the app from the ones you need to run it. For security, you must not run your container as the root user. You can do this by adding a non-root user and switching to it. For instance, putting RUN adduser, disabled-password, gecos "" appuser && chown -R appuser:appuser /app && USER appuser in your Dockerfile before you copy your code in. This simple step helps block privilege escalation attacks if your container ever gets compromised. A small image footprint and a secure user context are basic requirements for any production deployment.
““I kind of think of it as the AWS moment where we are taking the technology we’ve built for us, and we’re opening it up for more people to build on,” he said, adding this wouldn’t have been possible without developing its own robot fleet.”
2. Set Up Your AWS EKS Cluster
Getting an EKS cluster running has a few moving parts, but the most direct method is with eksctl, the official command-line tool for EKS. First, get the AWS CLI and kubectl installed and configured. Then grab eksctl by following the install guide over on its official site at eksctl.io. Be prepared to wait, as creating a cluster takes about 15 to 20 minutes, so go grab a coffee.
Here’s what a minimal cluster config looks like in a YAML file (let’s call it cluster.yaml):
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: my-python-cluster
region: us-east-1
version: "1.28"
nodeGroups:
- name: ng-1
instanceType: t3.medium
desiredCapacity: 2
minSize: 1
maxSize: 3
labels: {role: worker}
privateNetworking: true
To get it built, just run: eksctl create cluster -f cluster.yaml. This command provisions an EKS cluster named my-python-cluster in us-east-1 with two t3.medium worker nodes. You need to choose an instance type that makes sense for your app’s actual resource needs. Setting privateNetworking: true is a good move for security, as it keeps your worker nodes inside private subnets and off the public internet. After the cluster is ready, run aws eks update-kubeconfig, name my-python-cluster, region us-east-1 to point kubectl at it.
Common Mistake: Neglecting IAM Roles for Service Accounts (IRSA)
A mistake I see all the time is people not setting up IAM Roles for Service Accounts (IRSA) correctly. IRSA is what lets your Kubernetes pods assume an IAM role, which grants them specific permissions to talk to AWS services without you having to hardcode credentials into your container. This is absolutely necessary for securely accessing things like S3, DynamoDB, or AWS Secrets Manager. Before you deploy your app, make sure your EKS cluster’s OIDC provider is enabled and your Kubernetes ServiceAccount is pointing to the right IAM role. You can set it up with a command like eksctl create iamserviceaccount, name my-service-account, namespace default, cluster my-python-cluster, attach-policy-arn arn:aws:iam::123456789012:policy/my-s3-access-policy, approve. Don’t skip this for production environments.
3. Build and Push Your Docker Image to Amazon ECR
Okay, with the Dockerfile done and your EKS cluster running, it’s time to build your Docker image and push it to a container registry. Amazon Elastic Container Registry (ECR) is the obvious choice on AWS since it’s managed and integrates well. First, make a repo in ECR:
aws ecr create-repository, repository-name my-python-app, region us-east-1
Once that’s done, you’ll get back a repository URI that looks something like 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-python-app. Now, you need to authenticate your Docker client to ECR:
aws ecr get-login-password, region us-east-1 | docker login, username AWS, password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
Build your Docker image:
docker build -t my-python-app:latest .
And finally, tag it and push it up to ECR:
docker tag my-python-app:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-python-app:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-python-app:latest
Stop using just latest, especially in any CI pipeline. You need to use descriptive tags. A common approach is tagging images with the Git commit hash or a build number. This practice is what allows for precise rollbacks and makes version management sane.
4. Define Kubernetes Deployment and Service
Now that your image is waiting in ECR, you can tell EKS how to run it. You do this by creating Kubernetes manifest files for a Deployment and a Service. The Deployment file tells Kubernetes what you want running (like how many pod replicas), while the Service file defines a stable way to access those pods.
Here’s an example deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: python-app
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-python-app:latest
ports:
- containerPort: 8000
resources:
requests:
memory: "128Mi"
cpu: "200m"
limits:
memory: "256Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
This deployment creates three replicas of the Python app, pulling the image from ECR. It also sets resource requests and limits. If you don’t set these, your pods will fight over resources and your apps will become unstable. The readinessProbe is also a must-have. It tells Kubernetes when your app is actually ready to get traffic, which stops it from sending requests to pods that are still starting up. Just make sure your app exposes a /health endpoint for this to work.
And here’s the matching service.yaml:
apiVersion: v1
kind: Service
metadata:
name: python-app-service
spec:
selector:
app: python-app
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP
This creates a ClusterIP service, which gives your app an internal IP inside the EKS cluster on port 80 that forwards traffic to your containers on port 8000. To expose it to the internet, you’ll need an Ingress controller, which we’ll get to next.
Apply both of these files with kubectl apply -f deployment.yaml and kubectl apply -f service.yaml.
Pro Tip: Implement Horizontal Pod Autoscaling (HPA)
For scaling that doesn’t require you to wake up at 3 AM, set up Horizontal Pod Autoscaling (HPA). This lets Kubernetes automatically add or remove pods in your deployment based on CPU usage or other metrics. You could, for example, set up an HPA to target 50% CPU utilization. This is how your application can handle sudden traffic spikes without you doing anything, which is a major reason people use Kubernetes. You will need the Metrics Server installed in your cluster for CPU-based HPA to work.
5. Expose Your Application with an Ingress Controller
A ClusterIP service is only accessible inside your EKS cluster. To expose your Python app to the actual internet, you need an Ingress controller and an Ingress resource. The NGINX Ingress Controller is a popular and solid choice. You can install it pretty easily with Helm:
First, add the Helm repo:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
Then, install the controller itself:
helm install ingress-nginx ingress-nginx/ingress-nginx, namespace ingress-nginx, create-namespace
This command creates a Kubernetes service of type LoadBalancer, which in turn provisions an AWS Elastic Load Balancer (ELB) to route traffic from the internet to your Ingress controller. It can take a few minutes for the ELB to spin up. You can check its address with kubectl get services -n ingress-nginx.
With the Ingress controller running, you can define an ingress.yaml file:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: python-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: python-app-service
port:
number: 80
Apply this manifest with kubectl apply -f ingress.yaml. Don’t forget to go into your DNS provider and point myapp.example.com to the ELB address you got from the Ingress controller. This gives you a single entry point for all your traffic and lets you create routing rules based on hostnames or URL paths, which you’ll definitely need for any microservices architecture.
Common Mistake: Incorrect Ingress Annotations or DNS Configuration
One of the most frequent problems I see is people messing up Ingress annotations or their DNS records. If your Ingress isn’t routing traffic, the first thing to do is check the Ingress controller’s logs for errors (kubectl logs -n ingress-nginx <pod-name>). After that, double-check that your domain’s A record or CNAME record in Route 53 (or wherever you manage DNS) is correctly pointing to the ELB’s hostname. You also have to make sure the service.name and service.port.number in your Ingress manifest exactly match what’s in your Kubernetes Service definition.
6. Implement CI/CD with AWS CodePipeline and CodeBuild
Deploying to EKS by hand just isn’t sustainable. Automating your build, push, and deploy steps with AWS CodePipeline and AWS CodeBuild is how you build a real CI/CD pipeline. Once it’s set up, every code push will trigger an automated update to your EKS cluster, which cuts down on human error and gets features out faster.
First, you need a buildspec.yaml file in your Git repo. This file tells CodeBuild how to build your Docker image and push it to ECR:
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password, region $AWS_DEFAULT_REGION | docker login, username AWS, password-stdin $ECR_REPO_URI
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $ECR_REPO_URI:$CODEBUILD_RESOLVED_SOURCE_VERSION .
- docker tag $ECR_REPO_URI:$CODEBUILD_RESOLVED_SOURCE_VERSION $ECR_REPO_URI:latest
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $ECR_REPO_URI:$CODEBUILD_RESOLVED_SOURCE_VERSION
- docker push $ECR_REPO_URI:latest
- printf '[{"name":"python-app","imageUri":"%s"}]' $ECR_REPO_URI:$CODEBUILD_RESOLVED_SOURCE_VERSION > imagedefinitions.json
artifacts:
files: imagedefinitions.json
Next, you’ll set up a CodeBuild project that uses this `buildspec.yaml`. That project will need permissions to talk to ECR and push images, so attach a policy like AmazonEC2ContainerRegistryPowerUser to its service role. You’ll also define environment variables like AWS_DEFAULT_REGION and ECR_REPO_URI inside the CodeBuild project settings.
Finally, you create a CodePipeline. Your pipeline will usually have three stages:
- Source: This connects to your Git provider, like AWS CodeCommit, GitHub, or Bitbucket.
- Build: This stage triggers your CodeBuild project, which builds the Docker image and pushes it.
- Deploy: This stage uses an “Amazon EKS” action to update your Kubernetes Deployment with the new image. That action looks for an
imagedefinitions.jsonfile (which CodeBuild created) to know which container to update and what new image URI to use.
The “Amazon EKS” deploy action just handles patching your deployment with the new image tag. This automated workflow means that every validated code change gets deployed to your EKS cluster without anyone needing to run commands manually, which radically improves your deployment speed and reliability. A 2024 DORA report (cloud.google.com/devops) found that teams with mature CI/CD pipelines deploy hundreds of times more frequently and have much lower failure rates.
Pro Tip: Implement Rollbacks and Canary Deployments
Even with great automation, bad deployments happen. You can configure your Kubernetes Deployment with a revisionHistoryLimit to keep a record of past deployments, which lets you quickly roll back with kubectl rollout undo deployment/python-app-deployment. For more advanced setups, look into canary deployments. This means you deploy a new version to a small group of users first, watch how it performs, and then gradually roll it out to everyone else if it’s stable. Tools like Flagger or Argo Rollouts integrate directly with EKS to manage these complex deployment strategies, minimizing the risk of a bad update taking down your whole application.
Putting your Python apps in containers on AWS EKS gives you a strong, scalable, and automated platform. By following these steps, from getting the Dockerfile right to building a full CI/CD pipeline, you create a resilient foundation for your services that can actually keep up with changing demands. The process requires attention to detail in the configuration and a commitment to automation, but the long-term payoff in stability and agility is huge.
So why use Docker with Python on AWS EKS?
Using Docker with Python on AWS EKS gives you consistency between your dev, test, and prod environments, serious scalability through Kubernetes’ orchestration, and better resource isolation. It also makes managing dependencies much simpler and leads to faster, more reliable deployments.
What are the absolute must-haves for deploying a Python app to EKS?
The essentials are a Dockerfile for your Python app, an Amazon ECR repository to hold the Docker images, an AWS EKS cluster to run the containers, Kubernetes Deployment and Service manifests to define how it runs, and an Ingress controller to let outside traffic get to it.
How do I make my Docker images secure and small?
To keep images secure and optimized, start with official slim base images (like python:3.10-slim), use multi-stage builds to cut down the final size, and always run your container’s main process as a non-root user. You should also regularly scan your images for vulnerabilities with a tool like AWS ECR’s scanner or another third-party tool.
What does an Ingress controller actually do in EKS?
An Ingress controller is basically the traffic cop for your EKS cluster. It acts as a reverse proxy, taking incoming HTTP/HTTPS traffic and routing it to the right internal Kubernetes services based on rules you define in an Ingress resource. This is how you can expose a bunch of different services through a single IP address or domain name and handle things like SSL termination.
How does CI/CD work with EKS for a Python app?
CI/CD, usually with tools like AWS CodePipeline and CodeBuild, automates the whole loop: when you commit code, it automatically builds your Docker image, pushes that image to ECR, and then tells EKS to update your running Deployment with the new version. This automation is what gets rid of manual errors and lets you ship code faster and more safely.