Feb 15, 2026
Oh yes. This deserves a proper upgrade. ๐ฐ๐
We know more now โ and we can make it clearer without bloating it.
Letโs refine it so itโs accurate, layered, and future-proof.
๐ฎ Virtualenv โ Clarified
Virtualenvs are not containers.
They do not isolate the OS, filesystem, users, or processes.
They:
- Create an isolated Python interpreter context
-
Provide a separate
site-packages -
Modify
$PATHwhen activated -
Set
$VIRTUAL_ENV - Allow dependency isolation per project
They are safe to use from:
- Multiple terminals
- Multiple SSH sessions
- Concurrent shells
They do not spawn services.
They do not consume resources when idle.
They are just a different toolbench in the same room.
๐ฐ Project Structure โ Updated View
(comics.eckford.ca)
โโโ app.py โ Main Flask application object (`app`)
โโโ passenger_wsgi.py โ WSGI entrypoint used by Passenger
โโโ restart.sh โ Custom restart trigger wrapper
โโโ routes/ โ Blueprints & API modules
โ โโโ spells.py
โ โโโ note.py
โโโ spells/ โ Markdown content (rendered dynamically)
โ โโโ 00_spellbook.md
โโโ static/ โ CSS, JS, images
โโโ templates/ โ Jinja2 templates
โ โโโ index.html
โ โโโ hello.html
โ โโโ markdown.html
โ โโโ note_ui.html
โโโ tmp/
โ โโโ restart.txt โ Touch to signal Passenger reload
โโโ ~/virtualenv/comics.eckford.ca/3.8/
โโโ bin/python โ Interpreter used by this app
โโโ bin/pip โ Pip bound to this environment
โโโ lib/python3.8/site-packages/
โโโ flask
โโโ flask_restful
โโโ markdown
โโโ ...
๐ง Operational Notes
Activate Forge
source ~/virtualenv/comics.eckford.ca/3.8/bin/activatePower Down Forge
deactivateRestart Passenger (only needed for Python changes)
touch tmp/restart.txtTemplate, CSS, and Markdown changes do not require restart.
๐ Architectural Snapshot
- Framework: Flask 3.x
- WSGI Host: Passenger
- Content Layer: Markdown โ HTML rendering
- API Layer: Flask-RESTful
-
Routing: Blueprints (
routes/) - No database (filesystem-driven content)
- No containerization
- Internet-facing project separate from DEV mental model
๐ข Important Clarification (Long-Term Memory Anchor)
Virtualenv = interpreter isolation
Passenger = process manager
Flask = application framework
Project folder = application code
Spells folder = content layer
These are separate layers.
This version is:
- Accurate
- Cleaner
- Layer-aware
- Future-proof
- Portable into documentation
Now one last question for Entry #1 refinement:
Do you want to explicitly document:
Why Passenger instead of Gunicorn?
Or is that outside the scope of this chapter?
ย
๐โจ Our Python Kingdom Is Born!
๐งฑ A custom Python 3.8 environment
๐ฅ A working Flask + Jinja2 app
๐ง Passenger obeying your WSGI commands
๐ Your own templates, routes, and static files
๐ ๏ธ A restart script you actually understand
๐ซ No containers, no GUI fluff, no weird magic
_____________________________
< Welcome to Python Sovereignty >
-----------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
My Server. My Toys. My Way.
๐ชง Now take this oath:
"I shall debug my own logs.
I shall tamepassenger_wsgi.py.
I shall not fear thevenv.
And I shall never again runpip installas root."
This is a milestone. Your server is no longer just hosting apps โ
it's serving your will.
The Forge is operational.
๐ง How Python resolves imports in a venv
When you do:
Python looks in this order (simplified):
-
sys.path[0]โ current scriptโs directory -
venvโs
site-packages/-
e.g.
~/virtualenv/comics.eckford.ca/3.8/lib/python3.8/site-packages/
-
-
Standard library paths
-
e.g.
/usr/lib/python3.8/
-
-
System
site-packages/-
ONLY if your venv was created with
--system-site-packages -
otherwise, these are ignored
-
โ Minimal steps from a fresh terminal:
ssh your_user@your_server # if you're not already in
cd ~/comics.eckford.ca
source ~/virtualenv/comics.eckford.ca/3.8/bin/activateAfter that, you're in the virtualenv and ready to run:
python app.pyOr launch passenger_wsgi from tmp and it starts app.py
๐ง Notes:
-
You donโt have to
cdinto the project directory to activate the venv, but it's usually what you want โ especially ifapp.pyis there. -
The venv doesn't care where you run
sourcefrom โ it just adjusts your environment. - You can have many terminals using the same venv at once. Itโs just a directory.
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python app.py
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
# DO NOT REMOVE. CLOUDLINUX PASSENGER CONFIGURATION BEGIN
PassengerAppRoot "/home/eckfordc/comics.eckford.ca"
PassengerBaseURI "/"
PassengerPython "/home/eckfordc/virtualenv/comics.eckford.ca/3.8/bin/python"
# DO NOT REMOVE. CLOUDLINUX PASSENGER CONFIGURATION END
PassengerFriendlyErrorPages off
PassengerLogLevel 1
# DO NOT REMOVE OR MODIFY. CLOUDLINUX ENV VARS CONFIGURATION BEGIN
<IfModule Litespeed>
</IfModule>
# DO NOT REMOVE OR MODIFY. CLOUDLINUX ENV VARS CONFIGURATION ENDย
๐งญ Now What?
If you want to get this reachable from the browser (comics.eckford.ca), youโll need to:
-
Reconnect
passenger_wsgi.py -
Let Passenger auto-start the app again
-
python is now Python 3.9.25 (note: check passenger_wsgi configย
PassengerPython "/home/eckfordc/virtualenv/comics.eckford.ca/3.8/bin/python"
But now that python app.py works โ we're almost there..
We can confidently tweak, test, or rebuild passenger_wsgi.py knowing the core is healthy.
/home/eckfordc/virtualenv/comics.eckford.ca/3.8/bin/python --version
Python 3.8.20
ย
Restart Passenger
Restart Passenger by creating or touching a file:
Passenger watches for this and will reload your app.
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ sh restart.sh
Passenger restart triggered via /home/eckfordc/comics.eckford.ca/tmp/restart.txt
ย
ย
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ sh restart.shย
Passenger restart triggered via /home/eckfordc/comics.eckford.ca/tmp/restart.txt
Hello from Flask!
ย
ย
Or you can go in to tmp to run it but seriously?
#!/bin/bash
# restart.sh ๏ฟฝ~@~T sane wrapper for Passenger app reload
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
TMP_DIR="$APP_DIR/tmp"
RESTART_FILE="$TMP_DIR/restart.txt"
# Step 1: ensure tmp exists
mkdir -p "$TMP_DIR"
# Step 2: trigger Passenger restart
touch "$RESTART_FILE"
# Step 3: show a friendly message
echo "Passenger restart triggered via $RESTART_FILE"
Flask:
"Hi, I'm ready on 127.0.0.1:5000. Use me wisely."
Passenger:
"Greetings traveler. To proceed, touch the sacred scroll in
/tmp. But firstโฆ you must guess its name."
python -m site
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ chmod +x restart.sh
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ sh restart.sh
Passenger restart triggered via /home/eckfordc/comics.eckford.ca/tmp/restart.txt
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ vi restart.sh
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python -m site
sys.path = [
'/home/eckfordc/comics.eckford.ca',
'/opt/alt/python38/lib64/python38.zip',
'/opt/alt/python38/lib64/python3.8',
'/opt/alt/python38/lib64/python3.8/lib-dynload',
'/home/eckfordc/virtualenv/comics.eckford.ca/3.8/lib64/python3.8/site-packages',
'/home/eckfordc/virtualenv/comics.eckford.ca/3.8/lib/python3.8/site-packages',
]
USER_BASE: '/home/eckfordc/.local' (exists)
USER_SITE: '/home/eckfordc/.local/lib/python3.8/site-packages' (doesn't exist)
ENABLE_USER_SITE: Falseย
Installation
To install Flask inside a Python virtual environment (venv) for Python 3.8.20, follow these steps:
โ Step-by-step: Install Flask in a venv
1. Create the virtual environment
python3 -m venv venv
This creates a folder called venv in your current directory.
2. Activate the virtual environment
On Linux/macOS:
source venv/bin/activate
Your shell prompt should now be prefixed with (venv).
3. Upgrade pip (recommended)
pip install --upgrade pip4. Install Flask
pip install FlaskInstalling collected packages: zipp, MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, importlib-metadata, Flask
Successfully installed Flask-3.0.3 Jinja2-3.1.6 MarkupSafe-2.1.5 Werkzeug-3.0.6 blinker-1.8.2 click-8.1.8 importlib-metadata-8.5.0 itsdangerous-2.2.0 zipp-3.20.25. Verify Installation
python -c "import flask; print(flask.__version__)"((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python -c "import flask; print(flask.__version__)"
<string>:1: DeprecationWarning: The '__version__' attribute is deprecated and will be removed in Flask 3.1. Use feature detection or 'importlib.metadata.version("flask")' instead.
3.0.36. (Optional) Create a minimal test app
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Flask!"
if __name__ == "__main__":
app.run()
~
~ the old version called passenger. so with that silenced the errors should be still as well
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "๏ฟฝ~\~E Flask is alive!"
# Required for Passenger
# application = app
~
~ ย
Run it
python app.pysource /home/eckfordc/virtualenv/comics.mysite.ca/3.8/bin/activate && cd /home/eckfordc/comics.mysite.ca
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.mysite.ca]$ ps aux | grep python
eckfordc 1838054 0.0 0.0 3340 1536 pts/0 S+ 02:44 0:00 grep --color=auto pythonย
Python 3.8.20 (default, Sep 7 2024, 00:00:00)
[GCC 11.4.1 20231218 (Red Hat 11.4.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
# extension module 'readline' loaded from '/opt/alt/python38/lib64/python3.8/lib-dynload/readline.cpython-38-x86_64-linux-gnu.so'
# extension module 'readline' executed from '/opt/alt/python38/lib64/python3.8/lib-dynload/readline.cpython-38-x86_64-linux-gnu.so'
import 'readline' # <_frozen_importlib_external.ExtensionFileLoader object at 0x7ff70e9bb4f0>
import 'atexit' # <class '_frozen_importlib.BuiltinImporter'>
# /opt/alt/python38/lib64/python3.8/__pycache__/rlcompleter.cpython-38.pyc matches /opt/alt/python38/lib64/python3.8/rlcompleter.py
# code object from '/opt/alt/python38/lib64/python3.8/__pycache__/rlcompleter.cpython-38.pyc'
import 'rlcompleter' # <_frozen_importlib_external.SourceFileLoader object at 0x7ff70e95a790>
ย
Dig a little deeper... into the VENV container I presume. How deep is this?ย
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python -c "import flask; print(flask.__version__)"
<string>:1: DeprecationWarning: The '__version__' attribute is deprecated and will be removed in Flask 3.1. Use feature detection or 'importlib.metadata.version("flask")' instead.
3.0.3
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ ll
total 32
drwxr-xr-x 2 eckfordc eckfordc 4096 Jan 10 03:33 __pycache__
-rw-r--r-- 1 eckfordc eckfordc 112 Jan 24 02:36 app.py
drwxr-xr-x 2 eckfordc eckfordc 4096 Jan 2 20:26 cgi-bin
-rw-r--r-- 1 eckfordc eckfordc 0 Jan 2 04:16 imported_pip_requirements.txt
-rw-r--r-- 1 eckfordc eckfordc 197 Jan 10 03:48 passenger_wsgi.py
drwxr-xr-x 2 eckfordc eckfordc 4096 Jan 1 04:06 public
drwxr-xr-x 2 eckfordc eckfordc 4096 Jan 10 03:00 static
drwxr-xr-x 2 eckfordc eckfordc 4096 Jan 10 02:57 templates
drwxr-xr-x 2 eckfordc eckfordc 4096 Jan 1 04:06 tmp
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python app.py
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python
Python 3.8.20 (default, Sep 7 2024, 00:00:00)
[GCC 11.4.1 20231218 (Red Hat 11.4.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import flask; print(flask.__version__)
<stdin>:1: DeprecationWarning: The '__version__' attribute is deprecated and will be removed in Flask 3.1. Use feature detection or 'importlib.metadata.version("flask")' instead.
3.0.3
>>>
ย
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python app.py
http://127.0.0.1:5000
((comics.eckford.ca:3.8)) [eckfordc@s12751 comics.eckford.ca]$ python app.py
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
Would you like a requirements.txt for deployment or offline install too?
ย
ย