Last active
August 23, 2025 14:39
-
-
Save bockor/d751e252c5bb459bd4c6fe6934a46818 to your computer and use it in GitHub Desktop.
hosting-multiple-flask-apps-using-apache-mod_wsgi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Inspiration ... where did I start ?: | |
| # https://www.blopig.com/blog/2021/05/hosting-multiple-flask-apps-using-apache-mod_wsgi/ | |
| # https://www.rosehosting.com/blog/how-to-install-flask-on-ubuntu-22-04-with-apache-and-wsgi/?srsltid=AfmBOoqkm3GW5tXYBQywDTb_Y6SuLD0URVm9OC3uBqO4QhtSHpndMbz- | |
| # https://stackoverflow.com/questions/29882579/run-multiple-independent-flask-apps-in-ubuntu | |
| # https://serverfault.com/questions/357108/what-permissions-should-my-website-files-folders-have-on-a-linux-webserver | |
| sudo apt install apache2 -y | |
| sudo systemctl status apache2 | |
| sudo apt install libapache2-mod-wsgi-py3 | |
| apachectl -M | grep wsgi | |
| sudo apt install python3-pip | |
| sudo apt install python3.12-venv | |
| sudo mkdir /opt/ducksoup.retro/ | |
| sudo chown -R bruno ducksoup.retro/ | |
| sudo chgrp -R www-data ducksoup.retro/ | |
| sudo chmod -R 750 ducksoup.retro/ | |
| sudo chmod g+s ducksoup.retro/ | |
| Setting directories g+s makes all new files created in said directory have their group set to the directory's group. | |
| This can actually be really handy for collaborative purposes if you have the umask set so that files have group write by default. | |
| cd /opt/ducksoup.retro/ | |
| python3 -m venv .venv | |
| . .venv/bin/activate | |
| pip3 install flask | |
| vim /opt/ducksoup.retro/app.py | |
| =============================== | |
| from flask import Flask, render_template | |
| app = Flask(__name__) | |
| @app.route('/') | |
| @app.route('/moth') | |
| def index(): | |
| moth = "DuckSoup Sucks !" | |
| return render_template('index.html', moth=moth) | |
| =============================== | |
| vim /opt/ducksoup.retro/templates/index.html | |
| =============================== | |
| <html> | |
| <head> | |
| <title>DuckSoup Message Of the Day</title> | |
| </head> | |
| <body> | |
| <h3>{{moth}}</h3> | |
| </body> | |
| </html> | |
| =============================== | |
| vim /opt/ducksoup.retro/ducksoup.retro.wsgi | |
| =============================== | |
| import sys | |
| sys.path.insert(0,'/opt/ducksoup.retro') | |
| from app import app as application | |
| ============================== | |
| vim /etc/apache2/sites-available/ducksoup.retro.conf | |
| =============================== | |
| <VirtualHost *:80> | |
| ServerName ducksoup.retro | |
| DocumentRoot /opt/ducksoup.retro/ | |
| ErrorLog ${APACHE_LOG_DIR}/ducksoup.retro-error.log | |
| CustomLog ${APACHE_LOG_DIR}/ducksoup.retro-access.log combined | |
| WSGIDaemonProcess app user=www-data group=www-data threads=5 python-home=/opt/ducksoup.retro/.venv | |
| WSGIScriptAlias / /opt/ducksoup.retro/ducksoup.retro.wsgi | |
| <Directory /opt/ducksoup.retro> | |
| WSGIProcessGroup app | |
| WSGIApplicationGroup %{GLOBAL} | |
| Order deny,allow | |
| Require all granted | |
| </Directory> | |
| </VirtualHost> | |
| =============================== | |
| sudo a2ensite ducksoup.retro.conf | |
| sudo a2dissite 000-default.conf | |
| sudo apache2ctl configtest | |
| sudo systemctl reload apache2 | |
| +++++++++++++++++++++++++++++++ | |
| Now, let's add a new, dedicated api ... which returns json only | |
| sudo mkdir /opt/ducksoup-api.retro/ | |
| sudo chown -R bruno ducksoup-api.retro/ | |
| sudo chgrp -R www-data ducksoup-api.retro/ | |
| sudo chmod -R 750 ducksoup-api.retro/ | |
| sudo chmod g+s ducksoup-api.retro/ | |
| vim /opt/ducksoup-api.retro/app.py | |
| =============================== | |
| from flask import Flask, jsonify | |
| from datetime import datetime | |
| app = Flask(__name__) | |
| @app.route('/') | |
| def index(): | |
| version = datetime.now().strftime("%Y%m%dT%H%M") | |
| sitez = [ | |
| { 'id': 1, 'site': 'RED', 'location': 'ALPHA','comment': 'bla' }, | |
| { 'id': 2, 'site': 'BLUE', 'location': 'BRAVO','comment': 'blabla' }, | |
| { 'id': 3, 'site': 'GREEN','location': 'ALPHA', 'comment': 'blablabla' }, | |
| ] | |
| return jsonify({"version": version, "result":sitez}), 200 | |
| =============================== | |
| vim /opt/ducksoup-api.retro/ducksoup-api.retro.wsgi | |
| =============================== | |
| import sys | |
| sys.path.insert(0,'/opt/ducksoup-api.retro') | |
| from app import app as application | |
| =============================== | |
| vim /etc/apache2/sites-available/ducksoup.retro.conf | |
| =============================== | |
| <VirtualHost *:80> | |
| ServerName ducksoup.retro | |
| ErrorLog ${APACHE_LOG_DIR}/ducksoup.retro-error.log | |
| CustomLog ${APACHE_LOG_DIR}/ducksoup.retro-access.log combined | |
| WSGIDaemonProcess ducksoup_api user=www-data group=www-data threads=5 python-home=/opt/ducksoup.retro/.venv | |
| WSGIScriptAlias /api /opt/ducksoup-api.retro/ducksoup-api.retro.wsgi | |
| <Directory /opt/ducksoup-api.retro> | |
| WSGIProcessGroup ducksoup_api | |
| WSGIApplicationGroup %{GLOBAL} | |
| Require all granted | |
| </Directory> | |
| WSGIDaemonProcess ducksoup_app user=www-data group=www-data threads=5 python-home=/opt/ducksoup.retro/.venv | |
| WSGIScriptAlias / /opt/ducksoup.retro/ducksoup.retro.wsgi | |
| <Directory /opt/ducksoup.retro> | |
| WSGIProcessGroup ducksoup_app | |
| WSGIApplicationGroup %{GLOBAL} | |
| Require all granted | |
| </Directory> | |
| </VirtualHost> | |
| =============================== | |
| sudo apache2ctl configtest | |
| sudo systemctl reload apache2 | |
| curl -i http://ducksoup.retro | |
| ################### | |
| HTTP/1.1 200 OK | |
| Date: Sat, 16 Aug 2025 11:57:02 GMT | |
| Server: Apache/2.4.58 (Ubuntu) | |
| Content-Length: 145 | |
| Vary: Accept-Encoding | |
| Content-Type: text/html; charset=utf-8 | |
| <html> | |
| <head> | |
| <title>DuckSoup Message Of the Day</title> | |
| </head> | |
| <body> | |
| <h3>DuckSoup Sucks !</h3> | |
| </body> | |
| </html> | |
| =============================== | |
| curl -i http://ducksoup.retro/api/ | |
| ###################### | |
| HTTP/1.1 200 OK | |
| Date: Sat, 16 Aug 2025 10:36:28 GMT | |
| Server: Apache/2.4.58 (Ubuntu) | |
| Content-Length: 222 | |
| Content-Type: application/json | |
| {"result":[{"comment":"bla","id":1,"location":"ALPHA","site":"RED"},{"comment":"blabla","id":2,"location":"BRAVO","site":"BLUE"},{"comment":"blablabla","id":3,"location":"ALPHA","site":"GREEN"}],"version":"20250607T1245"} | |
| =============================== | |
| bruno@ducksoup:/opt$ tree | |
| . | |
| ├── ducksoup-api.retro | |
| │ ├── app.py | |
| │ └── ducksoup-api.retro.wsgi | |
| └── ducksoup.retro | |
| ├── app.py | |
| ├── ducksoup.retro.wsgi | |
| └── templates | |
| └── index.html | |
| =============================== | |
| Extra: How to solve the Apache Configuration Error AH00558: Could not reliably determine the server's fully qualified domain name | |
| ************************************ | |
| https://www.digitalocean.com/community/tutorials/apache-configuration-error-ah00558-could-not-reliably-determine-the-server-s-fully-qualified-domain-name | |
| sudo nano /etc/apache2/apache2.conf | |
| Add a line containing ServerName 127.0.0.1 to the end of the file: | |
| ServerName 127.0.0.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment