The problem: keeping many Python services up‑to‑date without manual restarts
When you run several Flask or Django apps on the same host, each needs its own virtual environment, possibly different Python versions, and you want to update one without touching the others. Doing this by hand — stopping a service, editing its config, starting it again — is error‑prone and creates downtime for the unchanged apps.
How uWSGI Emperor mode solves it
The Emperor is a master uWSGI process that watches a directory (the vassals folder) for .ini files. Each file describes one vassal app. When the Emperor sees a new or changed file it spawns a fresh vassal process; when the file is removed it shuts that vassal down. Because each vassal runs in its own address space, you can point them at different virtual environments or Python interpreters.
Under the hood the Emperor relies on the kernel’s inotify subsystem to get file‑system events, so it reacts instantly to touch, edits, or moves without polling.
Worked example: two Flask apps
1. Create the vassal directory (needs root or a user with sudo):
sudo mkdir -p /etc/uwsgi/vassals
2. Add a vassal config for app1 (/etc/uwsgi/vassals/app1.ini):
[uwsgi]
chdir = /opt/app1
module = wsgi:application
home = /opt/app1/venv # virtual‑env path
socket = /tmp/app1.sock
chmod-socket = 660
vacuum = true
3. Do the same for app2 (/etc/uwsgi/vassals/app2.ini) pointing to its own directory and venv.
4. Start the Emperor (run as a user that can read the vassal files and write to the log):
uwsgi --emperor /etc/uwsgi/vassals --daemonize /var/log/uwsgi-emperor.log
5. Verify startup: grep -i spawned /var/log/uwsgi-emperor.log should show lines for both vassals.
6. To update only app1, touch its config:
sudo touch /etc/uwsgi/vassals/app1.ini
7. Watch the log: you will see a graceful reload message for app1 only, while app2 continues serving requests.
Trade‑off and limitation
The Emperor adds a small resident memory footprint (the master process plus the inotify watch). More importantly, it requires a filesystem that supports inotify. Some container runtimes or network filesystems (e.g., NFS without proper options) may drop or delay events, causing the Emperor to miss changes. In those environments you must either enable polling (--emperor-poll-interval) or rely on external process managers.
You can check whether inotify is working by looking at the Emperor log after a touch; if no reload appears after a few seconds, increase the poll interval as a fallback:
uwsgi --emperor /etc/uwsgi/vassals --emperor-poll-interval 5 --daemonize /var/log/uwsgi-emperor.log
Actionable closing
If you operate more than one Python service on a host, give Emperor mode a try:
- Place each app’s .ini in a dedicated vassals directory.
- Start the Emperor with appropriate permissions and a log file.
- Validate that a simple touch triggers a reload only for the touched vassal.
- Monitor the Emperor log for any missing‑event warnings and enable polling if needed.
With these steps you get isolated, independently reloadable applications without writing custom restart scripts.
One correction to the "different Python interpreters" point: the home/virtualenv option swaps the package environment, not the interpreter. The Python binary is compiled into the uWSGI build (or its loaded plugin), so two vassals pointing at a 3.11 venv and a 3.12 venv will both run on whichever interpreter the Emperor's plugin was built against — the venv just has to match it.
To genuinely mix Python versions on one host, you need separately built plugins or binaries (naming varies by distro packaging) and typically one Emperor per interpreter, rather than just a different venv path per vassal.
Easy check: log sys.version at startup from each app, or inspect each vassal's stats server, and compare against what the venv path implies — this confirms which interpreter each vassal actually runs under.