Chapter 11: Application Hosting — WordPress, Flask and Express
11.2 Flask application with Gunicorn + Nginx
Students in my class run python app.py on the server and think the deployment is done. Look – that is only the development server. Let's do it the production way.
माझ्या क्लासमध्ये स्टुडंट्स सर्व्हरवर python app.py चालवतात आणि डिप्लॉयमेंट झालं असं समजतात. बघा – तो फक्त डेव्हलपमेंट सर्व्हर आहे. चला, प्रॉडक्शन पद्धतीने करूया.
मेरी क्लास में स्टूडेंट्स सर्वर पर python app.py चलाते हैं और समझते हैं कि डिप्लॉयमेंट हो गया. देखो – वो सिर्फ़ डेवलपमेंट सर्वर है. चलो, प्रोडक्शन वाले तरीके से करते हैं.
Why Gunicorn?
Friends, app.run() is only for development on your laptop. In production, Gunicorn runs several workers and Nginx stands in front.
मित्रांनो, app.run() हा फक्त तुमच्या लॅपटॉपवर डेव्हलपमेंटसाठी आहे. प्रॉडक्शनमध्ये Gunicorn अनेक वर्कर्स चालवतो आणि Nginx समोर उभा राहतो.
दोस्तों, app.run() सिर्फ़ आपके लैपटॉप पर डेवलपमेंट के लिए है. प्रोडक्शन में Gunicorn कई वर्कर्स चलाता है और Nginx आगे खड़ा रहता है.
flask run / app.run() starts a development server: single-threaded, not secure, not meant for production. Gunicorn ("Green Unicorn") is a production WSGI server that runs several worker processes of your app. Nginx sits in front to handle clients, static files and TLS.
Client ─► Nginx :80 ─┬─ /static/* → served directly from /opt/flaskdemo/static
└─ everything else → proxy_pass http://127.0.0.1:8000
│
Gunicorn master (systemd service)
├── worker 1 (Flask app)
├── worker 2
└── worker 3
Step 1 — Project structure
/opt/flaskdemo
├── app.py # Flask application
├── wsgi.py # entry point for Gunicorn
├── requirements.txt
├── templates/
│ └── index.html
├── static/
│ └── style.css
└── venv/ # virtual environment (not committed to Git)
Step 2 — Install and create the app
# AL2023 / CentOS: sudo yum install -y python3 python3-pip git nginx
# Ubuntu: sudo apt install -y python3 python3-venv python3-pip git nginx
sudo mkdir -p /opt/flaskdemo/{templates,static} && sudo chown -R $USER:$USER /opt/flaskdemo
cd /opt/flaskdemo
python3 -m venv venv
./venv/bin/pip install flask gunicorn
./venv/bin/pip freeze > requirements.txt
cat > app.py <<'EOF'
import socket
from datetime import datetime
from flask import Flask, render_template, jsonify
app = Flask(__name__)
@app.route("/")
def home():
return render_template("index.html", host=socket.gethostname(), now=datetime.now())
@app.route("/api/info")
def info():
return jsonify(app="flaskdemo", host=socket.gethostname())
EOF
cat > wsgi.py <<'EOF'
from app import app
if __name__ == "__main__":
app.run()
EOF
cat > templates/index.html <<'EOF'
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Flask on EC2</title>
<link rel="stylesheet" href="/static/style.css"></head>
<body><div class="card"><h1>Flask + Gunicorn + Nginx</h1>
<p>Served by host <b>{{ host }}</b> at {{ now.strftime("%d-%m %H:%M:%S") }}</p></div></body></html>
EOF
cat > static/style.css <<'EOF'
body { font-family: Arial, sans-serif; background: #eef3f9; }
.card { max-width: 600px; margin: 60px auto; background: #fff; padding: 30px; border-radius: 10px; }
EOF
If your code is on GitHub, replace the file-creation steps with git clone https://github.com/<you>/<repo>.git /opt/flaskdemo and ./venv/bin/pip install -r requirements.txt.
Step 3 — Test Gunicorn manually
cd /opt/flaskdemo
./venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 wsgi:app &
sleep 2 && curl -s http://127.0.0.1:8000/api/info
kill %1
wsgi:app means "module wsgi.py, object app". A common rule for workers is (2 × number of CPUs) + 1.
Step 4 — systemd service
sudo tee /etc/systemd/system/flaskdemo.service > /dev/null <<EOF
[Unit]
Description=Gunicorn instance serving flaskdemo
After=network.target
[Service]
User=$USER
Group=$USER
WorkingDirectory=/opt/flaskdemo
Environment="PATH=/opt/flaskdemo/venv/bin"
ExecStart=/opt/flaskdemo/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 --access-logfile - wsgi:app
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo service flaskdemo start
sudo systemctl enable flaskdemo
sudo service flaskdemo status
Step 5 — Nginx configuration
# AL2023 / CentOS path shown; on Ubuntu use /etc/nginx/sites-available/flaskdemo + symlink + remove default
sudo tee /etc/nginx/conf.d/flaskdemo.conf > /dev/null <<'EOF'
server {
listen 80;
server_name YOUR_SERVER_NAME;
location /static/ {
alias /opt/flaskdemo/static/;
expires 7d;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
sudo sed -i "s/YOUR_SERVER_NAME/$(curl -s https://checkip.amazonaws.com)/" /etc/nginx/conf.d/flaskdemo.conf
sudo service nginx start
sudo systemctl enable nginx
sudo nginx -t && sudo service nginx reload
CentOS Stream 9 SELinux extras:
sudo setsebool -P httpd_can_network_connect 1
sudo semanage fcontext -a -t httpd_sys_content_t "/opt/flaskdemo/static(/.*)?"
sudo restorecon -Rv /opt/flaskdemo/static
Open http://<PUBLIC_IP>/ — you should see the styled page.
Ravindra Bagale's Tip
Keep your code in Git from day one, even for small college projects. Deployment then becomes git pull + restart, and if something breaks you can go back instantly with git checkout <previous-commit>. Interviewers also like seeing a clean GitHub history.
Ravindra Bagale's Tip – मराठी
पहिल्या दिवसापासून code Git मध्ये ठेवा, छोटे college projects असले तरी. मग deployment म्हणजे git pull + restart, आणि काही तुटलं तर git checkout <previous-commit> ने लगेच मागे जाता येतं. Interviewers ना पण स्वच्छ GitHub history बघायला आवडते.
Ravindra Bagale's Tip – हिंदी
पहले दिन से code Git में रखो, छोटे college projects हों तब भी. फिर deployment मतलब git pull + restart, और कुछ टूटा तो git checkout <previous-commit> से तुरंत पीछे जा सकते हो. Interviewers को भी साफ़ GitHub history देखना पसंद है.
Step 6 — Updating the application
cd /opt/flaskdemo
git pull # if deployed from Git
./venv/bin/pip install -r requirements.txt
sudo service flaskdemo restart
journalctl -u flaskdemo -n 50 --no-pager # check logs
Ravindra Bagale's Tip
Many students mix up app:app and wsgi:app in Gunicorn and get ModuleNotFoundError in journalctl. wsgi:app means the file wsgi.py and the app object inside it. If the service fails, first look at journalctl -u flaskdemo -n 50 --no-pager.
Ravindra Bagale's Tip – मराठी
खूप students Gunicorn मध्ये app:app आणि wsgi:app मध्ये गोंधळतात, आणि journalctl मध्ये ModuleNotFoundError येतं. wsgi:app म्हणजे wsgi.py file आणि त्यातला app object. Service fail झाली तर आधी journalctl -u flaskdemo -n 50 --no-pager बघा.
Ravindra Bagale's Tip – हिंदी
बहुत students Gunicorn में app:app और wsgi:app में उलझ जाते हैं, और journalctl में ModuleNotFoundError आता है. wsgi:app मतलब wsgi.py file और उसके अंदर का app object. Service fail हो तो पहले journalctl -u flaskdemo -n 50 --no-pager देखो.
Lab
Chala, Flask demo deploy kara aani http://<PUBLIC_IP>/ var styled page disto ka te bagha. Mag templates/index.html madhe <h1> madhe "Mauli Dairy" liha, sudo service flaskdemo restart kara aani badal disto ka te check kara.
Got it? Gunicorn runs the code, Nginx stands in front, and systemd never lets it stop. Now let's move on to Express.
समजलं का? Gunicorn कोड चालवतो, Nginx समोर उभा राहतो, आणि systemd त्याला बंद पडू देत नाही. आता पुढे जाऊया Express कडे.
समझ आया? Gunicorn कोड चलाता है, Nginx आगे खड़ा रहता है, और systemd उसे बंद नहीं होने देता. अब आगे चलते हैं Express की तरफ़.