The idea: run a Flask app behind Apache httpd, the way many teams still host Python on an existing Apache box. This repo is the small, current version of that setup: a Flask 3 app factory with three JSON endpoints, an Apache 2.4 virtual host for mod_wsgi, and a Docker Compose stack that runs it end to end.
- mod_wsgi runs the app inside Apache worker processes. One moving part less at runtime, but mod_wsgi must be compiled against the same Python as your venv. With Python 3.14 that usually means building it yourself, so it is the bare-metal path here (
flask_apache.confandflask_apache.wsgi). - gunicorn behind mod_proxy keeps Python in its own process and lets Apache forward requests to it. Any stock
httpdimage works, so this is what Docker Compose uses.
Both serve the same app and the same endpoints.
| Path | Returns |
|---|---|
/ |
greeting message |
/health |
{"status": "UP"} |
/info |
service name, version, start time, server stack |
Every response is JSON and includes a UTC timestamp.
make venv
make dev # http://localhost:3100/health
make testcp .env.example .env # set SECRET_KEY
make docker # http://localhost:3100/health
make docker-downsudo mkdir -p /var/www/flask_apache && sudo chown $USER /var/www/flask_apache
git clone https://github.com/wallaceespindola/flask-apache /var/www/flask_apache
cd /var/www/flask_apache
# keep the uv-managed Python outside $HOME so www-data can read it
sudo mkdir -p /opt/uv-python && sudo chown $USER /opt/uv-python
export UV_PYTHON_INSTALL_DIR=/opt/uv-python
uv sync --no-dev
sudo apt install apache2 apache2-dev
# mod_wsgi must match the venv Python, so build it from the venv:
uv pip install mod_wsgi
.venv/bin/mod_wsgi-express module-config | sudo tee /etc/apache2/mods-available/wsgi.load
sudo a2enmod wsgi
echo "Listen 3100" | sudo tee -a /etc/apache2/ports.conf
sudo cp flask_apache.conf /etc/apache2/sites-available/
echo 'export SECRET_KEY=change-me' | sudo tee -a /etc/apache2/envvars
sudo chown -R www-data /var/www/flask_apache
sudo a2ensite flask_apache && sudo systemctl restart apache2Edit ServerName in flask_apache.conf first. SECRET_KEY goes in /etc/apache2/envvars because the daemon process inherits Apache's environment. SetEnv does not work here: it only reaches the per-request WSGI environ, not os.environ at import time. Call .venv/bin/mod_wsgi-express directly, since uv run would reinstall the dev tools. These steps were tested on Ubuntu 24.04 with Python 3.14.
| Variable | Purpose | Default |
|---|---|---|
SECRET_KEY |
Flask session signing key | empty |
HTTP_PORT |
Host port Apache publishes in Docker Compose | 3100 |
make format, make lint, make typecheck and make test run black and isort, ruff, mypy in strict mode, and pytest with an 80% coverage floor. CI runs the same checks.
If you find this project helpful, please consider:
Star the repo on GitHub if it helps your Flask and Apache work. It motivates me a lot!
Wallace Espindola, Solution Architect
- Email: wallace.espindola@gmail.com
- LinkedIn: https://www.linkedin.com/in/wallaceespindola/
- GitHub: https://github.com/wallaceespindola/
- Twitter/X: @wsespindola
- Substack: Code & Coffee by Wallace Espindola
- Medium: https://medium.com/@wallaceespindola
- Dev.to: https://dev.to/wallaceespindola
- DZone: https://dzone.com/users/1254611/wallacese.html
- Speaker Deck: https://speakerdeck.com/wallacese
- Speaker profile: https://sessionize.com/wallace-espindola/
- Company: www.wtechitsolutions.com
- Buy Me a Coffee: https://www.buymeacoffee.com/wallace.espindola
- This project is released under the Apache 2.0 License.
- See the LICENSE file for details.
- Copyright © 2026 Wallace Espindola.
