The Deployment Gauntlet: From localhost
to Live on AWS (Part 2)
Welcome back! In Part 1, we laid the foundation for our MLOps project by building a modular, reproducible machine learning pipeline that worked flawlessly on a local machine. Now for the "easy" part, right? Taking that pristine local system and deploying it to the cloud.
This is where theory meets the harsh, humbling reality of production infrastructure. This is the story of the deployment gauntlet.
The AWS Deployment Nightmare: When the Cloud Humbles You
If there's one part of this project that truly tested my resolve, it was the AWS deployment. The plan was simple: containerize the application with Docker, push it to AWS ECR, and run it on an EC2 instance, all automated with GitHub Actions.
The reality? A multi-day debugging marathon. Setting up the self-hosted runner on EC2 was the first hurdle. The documentation makes it sound easy, but it involved countless cycles of configuring IAM permissions, installing Docker, and troubleshooting network settings just to get the runner to connect to GitHub.
The Great Connectivity Mystery: A Debugging Story
The most frustrating challenge arrived when the CI/CD pipeline finally ran successfully. Green checkmarks everywhere. The Docker container was running on EC2. All services appeared healthy. Yet, when I tried to access the FastAPI application from its public IP address... connection timeouts.
The problem wasn't in my application code. It was a subtle, yet critical, detail of container networking. After hours of checking every layer—EC2 security groups, Docker port mappings, even the instance's firewall—I had a breakthrough. The FastAPI application was binding to localhost
(127.0.0.1
) by default, which means it would only accept connections from within the container itself.
The fix was a single line of code, but the lesson was profound.
# The critical configuration for a containerized web server
if __name__ == "__main__":
# Binding to 0.0.0.0 tells the app to accept connections from any IP,
# including those from outside the Docker container.
uvicorn.run(app, host="0.0.0.0", port=8080)
This experience taught me that in the cloud, the problem is often not in your code, but in the complex interactions between the layers of infrastructure it runs on.
Automating Everything with a CI/CD Pipeline
With the connectivity mystery solved, I could finally appreciate the power of the CI/CD pipeline. The GitHub Actions workflow automated every step, from building the Docker image to deploying it on the self-hosted EC2 runner.
# A snippet from the GitHub Actions workflow for deployment
jobs:
Continuous-Deployment:
needs: build-and-push-ecr-image
runs-on: self-hosted # This job runs on our EC2 instance
steps:
- name: Pull latest image from ECR
run: docker pull ${{ secrets.AWS_ECR_LOGIN_URI }}/...
- name: Stop and remove old container if it exists
run: docker stop network_security || true && docker rm network_security || true
- name: Run Docker Image to serve users
run: |
docker run -d -p 8080:8080 --name=network_security \
-e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
# ... other environment variables
${{ secrets.AWS_ECR_LOGIN_URI }}/...
This automation is the heart of MLOps. It ensures that every deployment is consistent, repeatable, and requires zero manual intervention.
Key Takeaways: Lessons for Future Projects
This project was challenging, frustrating, and ultimately one of the most rewarding things I've ever built. Here are the key lessons I'm taking with me:
1. Modular architecture is not a luxury; it's a necessity. It is the foundation for a maintainable and debuggable system.
2. Cloud deployment is an infrastructure challenge, not just a code challenge. You have to understand the entire stack, from IAM permissions to container networking.
3. Invest in logging and exception handling from day one. When things break in production, good logs are the only thing that will save you.
4. True MLOps is about automation. The goal is to build a system where a
git push
can safely and reliably trigger a new model deployment.
The journey of building this MLOps pipeline taught me that the real value lies not just in the final working system, but in the deep, practical understanding gained through the struggle of building it from the ground up.
Want to dive into the code yourself? You can find the entire project, including a detailed README with setup instructions, on my GitHub repository.