Deploy Flask on EC2
Deploy a Python Flask app on EC2 — AWS Cloud & DevOps with Python.
Focus: deploy a python flask app on ec2
You’ve built a Flask app locally, maybe even run it with flask run and felt that little thrill when localhost:5000 responded. But the moment you need someone else to see it, or a service to call it, your laptop isn’t good enough. Deploying a Python Flask app on EC2 moves your code from a toy to a real, internet-facing service — and it’s the first time you must think like a DevOps engineer, not just a Python developer. This lesson takes you from a local app.py to a production-ready web app on AWS EC2, handling the networking, packaging, and process management that separates a demo from a deployment.
The problem this lesson solves
Every Python developer hits the same wall: the app works on my machine. But your machine isn’t always on, isn’t publicly reachable, and doesn’t have the reliability you need for real users. Deploying to EC2 solves three concrete problems:
- Accessibility — EC2 gives you a public IP address, so anyone on the internet can request your app.
- Persistence — An EC2 instance runs 24/7, independent of your laptop’s sleep cycle or coffee shop Wi-Fi.
- Scalability — You can start small (t2.micro) and upgrade to a stronger instance type as your user base grows, or add more instances later.
But EC2 also introduces new pain points: security groups that default to blocking everything, SSH keys that confuse beginners, and the dreaded ModuleNotFoundError when your local virtualenv doesn’t exist on the server.
Pro tip: Deploying to EC2 isn’t just about copying files — it’s about creating a reliable, repeatable environment. Treat it as the first exercise in infrastructure as code.
Core concept / mental model
Think of EC2 as a rented remote computer — a virtual machine that lives in Amazon’s data center. You don’t own the hardware; you pay for time on it, and you control everything that runs inside it, from the operating system to the Python packages.
When you deploy a Flask app, you are performing three main steps:
- Set up the environment — install Python, create a virtual environment, install dependencies.
- Transfer your code — move your Flask app files onto the instance.
- Run the app persistently — start a production WSGI server (like Gunicorn) and keep it running.
The mental model for a production Flask deployment is a three-layer stack:
- Web server / reverse proxy (Nginx) — handles HTTP requests from the internet and forwards them to Gunicorn.
- WSGI server (Gunicorn) — runs the Python Flask application.
- Application code — your app logic, templates, static files.
Why not just run flask run on EC2? The Flask development server is single-threaded and not designed for production. Gunicorn handles multiple concurrent requests, and Nginx serves static files efficiently and adds security. This is the standard stack used by production Flask deployments.
How it works step by step
Before you can deploy, you need an EC2 instance. Here’s the logical sequence:
- Launch an EC2 instance — Choose an Amazon Linux or Ubuntu AMI, pick a t2.micro (free tier if available), and create a key pair for SSH access.
- Configure security groups — Allow inbound SSH (port 22) from your IP, and HTTP (port 80) from anywhere.
- SSH into the instance — Use your
.pemkey file:ssh -i mykey.pem ec2-user@<public-ip>(for Amazon Linux) orubuntu@<public-ip>(for Ubuntu). - Install Python and dependencies — Update the system, install Python 3, pip, and virtualenv.
- Transfer your code — Use
scpor clone a git repository onto the instance. - Set up a virtual environment and install requirements —
python3 -m venv venv,source venv/bin/activate,pip install -r requirements.txt. - Run Gunicorn — Start the WSGI server, binding to a port (e.g., 8000).
- Set up Nginx as a reverse proxy — Pass requests from port 80 to Gunicorn.
- Ensure persistence — Use a process manager like
systemdto keep Gunicorn running across reboots.
Each step has a clear cause-and-effect: if you skip installing packages, your app crashes with ModuleNotFoundError. If you don’t open port 80, your app is unreachable. If you forget to configure systemd, your app dies when you close SSH.
Hands-on walkthrough
This walkthrough assumes you have a simple Flask app ready. Create a test file locally if you don’t. Here’s a minimal app.py:
# app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def home():
return jsonify(message="Hello from EC2!")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Your requirements.txt should include Flask and gunicorn.
Flask==3.0.0
gunicorn==21.2.0
Step 1: Launch the EC2 instance
Use the AWS Console or the AWS CLI. For CLI users:
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \
--instance-type t2.micro \
--key-name my-keypair \
--security-group-ids sg-12345678
Replace the AMI ID with a current Amazon Linux 2 AMI. If using the console, select Amazon Linux 2, choose t2.micro, and create a new key pair.
Step 2: Configure security group
Your instance must allow inbound traffic. Add rules:
- SSH (port 22) — Source: your IP (e.g.,
203.0.113.1/32) - HTTP (port 80) — Source:
0.0.0.0/0
Step 3: SSH into the instance
chmod 400 my-keypair.pem
ssh -i my-keypair.pem ec2-user@<your-public-ip>
You’ll see a shell prompt. Now you’re on your EC2 instance.
Step 4: Install Python and dependencies
sudo yum update -y
sudo yum install -y python3 python3-pip nginx
sudo pip3 install virtualenv
Step 5: Transfer your code
Open a new terminal on your local machine and use scp:
scp -i my-keypair.pem app.py requirements.txt ec2-user@<your-public-ip>:/home/ec2-user/
Alternatively, if your code is in Git, clone on the instance:
git clone https://github.com/yourusername/my-flask-app.git
cd my-flask-app
You can combine the commands in one go:
mkdir -p ~/app
cd ~/app
# copy app.py and requirements.txt here
Step 6: Create virtualenv and install requirements
cd ~/app
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
Step 7: Run Gunicorn
Test your app manually first:
gunicorn --workers 3 --bind 0.0.0.0:8000 app:app
What to expect: Gunicorn starts and logs a listening address. Open
http://<your-public-ip>:8000in your browser. If you see the JSON response, your app is running!
Step 8: Set up Nginx as a reverse proxy
Create a file /etc/nginx/conf.d/flask.conf with:
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Then start and enable Nginx:
sudo systemctl start nginx
sudo systemctl enable nginx
Now visit http://<your-public-ip> — your app is served through Nginx on port 80.
Step 9: Make Gunicorn persistent with SystemD
Create /etc/systemd/system/flaskapp.service:
[Unit]
Description=Gunicorn instance to serve my Flask app
After=network.target
[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/home/ec2-user/app
Environment="PATH=/home/ec2-user/app/venv/bin"
ExecStart=/home/ec2-user/app/venv/bin/gunicorn --workers 3 --bind unix:flaskapp.sock app:app
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl start flaskapp
sudo systemctl enable flaskapp
Now your app survives reboots and SSH disconnects.
Compare options / when to choose what
The deployment stack you choose depends on your needs. Here’s a quick comparison:
| Option | Pros | Cons | Best for |
|---|---|---|---|
| Gunicorn alone | Simple, no extra config | No static file serving, no security features | Internal tool, quick test |
| Gunicorn + Nginx | Production-ready, efficient static file serving, loads balancing | More setup complexity | Public web apps |
| Docker on EC2 | Container portability, consistent environment | Adds Docker learning curve, more overhead | Microservices, teams already using Docker |
| PaaS (Elastic Beanstalk) | Managed, auto-scaling | Less control, potential cost | Small teams without dedicated DevOps |
| Serverless (Lambda) | No server management, scales automatically | Cold start latency, limited execution time | Event-driven or lightweight APIs |
When to choose what:
- Learning / prototype: Gunicorn alone is enough.
- Production Flask app: Always add Nginx as a reverse proxy.
- Team with Docker skills: Docker simplifies environment reproduction.
- Startup with no DevOps: Elastic Beanstalk abstracts EC2 entirely.
Troubleshooting & edge cases
Here are the most common issues when deploying Flask on EC2:
ModuleNotFoundError: No module named 'flask'— You forgot to activate the virtualenv or installrequirements.txt. Check:source venv/bin/activateandpip install -r requirements.txt.- App unreachable on port 8000 — Your security group doesn’t allow inbound traffic on that port. Either add a rule for port 8000 or use Nginx on port 80.
- Port 80 returns “Connection refused” — Nginx isn’t running, or the proxy configuration doesn’t match the Gunicorn bind address. Check
systemctl status nginxandnginx -t. - Permission errors on
.pemfile — On macOS/Linux, runchmod 400 your-key.pem. Address already in use— Another process is on the port. Kill it withsudo lsof -i :8000andkill PID.- App crashes after SSH disconnects — You ran Gunicorn in the foreground without systemd. Use a process manager or
nohup(though systemd is better). - EC2 public IP changes after reboot — If you stop and start your instance, the IP changes (unless you have Elastic IP). For production, allocate an Elastic IP.
Pro tip: Use a custom AMI once you have a perfected setup. That way, you can launch new instances with everything pre-installed in minutes.
What you learned & what's next
You now understand the core of deploying a Python Flask app on EC2: creating an instance, securing it, transferring code, running a WSGI server, and making it persistent. You can explain the three-layer architecture and make informed choices among Gunicorn, Nginx, Docker, and managed services. You’ve completed a hands-on deployment that takes you from zero to a live URL.
This is a foundational skill for every DevOps engineer. Next, you’ll tackle automating deployments with CI/CD — imagine pushing to GitHub and having your EC2 instance update itself. You’ll also explore Infrastructure as Code with Terraform to build and destroy environments reliably. This lesson gave you the manual steps; the next lessons build automation on top.
Keep your EC2 instance running for the next exercises — you’ll need it to practice rolling updates and load balancing. If you’re done, terminate the instance to avoid costs, but remember to note the steps for when you need to redeploy!
Practice recap
Now that you've deployed manually, destroy the instance and try again — but this time, use a small shell script to automate the setup steps. Then, attempt to attach an Elastic IP to your instance to ensure a stable URL. Finally, experiment with changing the number of Gunicorn workers and observe how your app handles concurrent requests.
Common mistakes
- Forgetting to open the right security group ports — your app runs locally but is unreachable from the internet.
- Running
flask runon EC2 — the development server is not designed for production traffic and will crash under load. - Not using a virtual environment on the server — you'll face
ModuleNotFoundErroror dependency conflicts. - Skipping systemd setup — your app dies when you close the SSH session, and you wonder why it's not working later.
Variations
- Use Docker on EC2 to containerize the Flask app, making environment setup repeatable and portable.
- Replace Nginx with Apache or use AWS Application Load Balancer for traffic distribution across multiple instances.
- Automate the entire deployment with Infrastructure as Code using Terraform or AWS CloudFormation instead of manual CLI steps.
Real-world use cases
- Hosting a REST API for a small startup that needs a public endpoint for mobile clients at minimal cost.
- A side-project web app with low traffic, using the AWS free tier to run without recurring charges.
- A demo environment for a client to preview a new Flask-based dashboard before moving to production.
Key takeaways
- Deploying Flask on EC2 requires three layers: Nginx (web server), Gunicorn (WSGI), and your app code.
- A security group is a firewall — you must explicitly open ports for SSH and HTTP.
- Always use a virtual environment on EC2 to manage dependencies cleanly.
- Run Gunicorn with systemd to keep your app alive through reboots and SSH disconnects.
- Nginx adds production-level features like static file serving and reverse proxying.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.