Until recently, I've used pretty much the same LAMP environment I installed when I started with GNU/Linux. It had many drawbacks, the most important being cryptic configuration (hello, mod_rewrite) and the need to edit files as root when adding new applications using VirtualHosts. Also, running Python applications is quite a pain with Apache and mod_wsgi - most of the time, I just went with whatever development server my framework offered.
Then I stumbled upon uWSGI. I was blown away with how versatile this web application server is - it supports a plethora of programming languages, has a powerful routing system and loads of other features. I experimented with it for some time and this is the result - a flexible web development environment using uWSGI and Nginx.
Pluggable applications with uWSGI
First, we'll start uWSGI in Emperor mode. It will monitor a path and make sure
that all uWSGI configuration files found there are running. It also reloads the
applications when their configuration changes. We'll be putting our applications
in ~/WWW
, with the following structure:
~/WWW
├── app1
│ ├── app1.ini
│ ├── file1
│ └── file2
└── app2
├── app2.ini
├── file1
└── file2
As we see, there is a folder for each application that contains its sources and also a uWSGI configuration file. The name of this file is used as the vassal name by the Emperor. We'll start the Emperor now:
uwsgi --emperor "~/WWW/*/*.ini" \
--emperor-on-demand-directory /tmp/uwsgi/ \
--logto %h/WWW/uwsgi.log \
--vassals-set socket-chmod=666 \
--vassals-set idle=900 \
--vassals-set die-on-idle=1
The --emperor-on-demand-directory
flag tells the Emperor to make a UNIX domain
socket for each application (named <vassal_name>.socket
) in given directory
and activate the application when someone connects to that socket. When we
combine this with --idle
and --die-on-idle
on the vassals, we get a
webserver that automatically stops unused applications and their daemons.
We also need nginx to have read and write access to the sockets - that's why we
set their permissions to 666. If we wanted to be a bit more strict, we could
make a group for our user and nginx and chown
the sockets to this group.
Now, let's add an application! This one will be in PHP, but use whatever you feel like. First, the "application" itself:
# ~/WWW/testapp/index.php
<?php
echo "Hello from uWSGI";
Now, we'll write the configuration file that tells uWSGI how to run it.
# ~/WWW/testapp/testapp.ini
[uwsgi]
chdir = %d
# PHP settings
plugin = 0:php
php-docroot = %d
check-static = %d
php-app = %d/index.php
Noticed the 0:
in the plugin
option? This little detail is actually really
important - it tells uWSGI to route HTTP requests to the PHP plugin. For more
details, see the documentation for modifier1
.
The Emperor should now notice the configuration file and run our application. It is however bound to a UNIX domain socket - to access it, we need a webserver to forward actual HTTP requests to it.
Nginx - the fixed part
Nginx will serve as a gateway to our applications. It should pass requests to
subdomains on localhost to corresponding UNIX domain sockets that belong to the
uWSGI vassals - requests on myapp.localhost
will be passed to
/tmp/uwsgi/myapp.socket
.
# nginx.conf
http {
server {
listen 80;
server_name ~^(?<app>[^.]+)\.localhost;
location / {
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi/$app.socket;
}
}
}
This is not a complete configuration file - I only included the relevant parts.
That being said, you should now be able to open testapp.localhost
in a web
browser and see the output!
Bonus - running uWSGI with systemd
If you're running a recent GNU/Linux distribution, it's likely that you use systemd. One of its better features is that it lets users run their own services without any special privileges. We'll use that to make sure our uWSGI Emperor is always running. First, we create a systemd unit file:
# ~/.config/systemd/user/uwsgi-emperor.service
[Unit]
Description=uWSGI Emperor
[Service]
ExecStartPre=/usr/bin/mkdir -p /tmp/uwsgi/
ExecStartPre=/usr/bin/chmod 777 /tmp/uwsgi/
ExecStart=/usr/bin/uwsgi \
--emperor "%h/WWW/*/*.ini" \
--emperor-on-demand-directory /tmp/uwsgi/ \
--logto %h/WWW/uwsgi.log \
--vassals-set socket-chmod=666 \
--vassals-set idle=900 \
--vassals-set die-on-idle=1
[Install]
WantedBy=default.target
Now we'll make sure that the uWSGI Emperor starts when we log in:
$ systemctl --user daemon-reload
$ systemctl --user enable uwsgi-emperor
$ systemctl --user start uwsgi-emperor
Conclusion
We have set up a development environment where we can program in Python, Ruby, PHP and tons of other languages (I also used it to run Pelican when I wrote this post). All our code runs under a regular user (if that wasn't enough, uWSGI can sandbox it using lxc). The applications are accessible with pretty URLs on the standard HTTP port, which makes it easy to test them on various devices.