Do I need Apache? If so, I'm lost configuring it.

In order to serve static files, I could swear that I read on the Django documentation that I should use Apache, although I can’t find the quote now. (It might be the passage I’ve copied below under “Do the docs assume a VirtualHost?”)

My site will provide an interface to an economic model. A user will enter changes to the tax code, and then the model will generate some graphs and tables, which will reside as static files on the server until deleted a few weeks later. I want the user to be able to view the graphs and tables in the browser, as well as download them. The site will be hosted from a Debian-based Docker container on a rented bare-metal server.

Do I need Apache? If so, I’m having these questions about configuring it:

Do the docs assume a VirtualHost?

The Django docs for using Apache and modwsgi (I’m somehow not allowed to post the link, so I’ve put it in a reply after this post) include the following passage:

We recommend using a separate Web server – i.e., one that’s not also running
Django – for serving media. Here are some good choices:

* Nginx
* A stripped-down version of Apache

If, however, you have no option but to serve media files on the same Apache
VirtualHost as Django, you can set up Apache to serve some URLs as static
media, and others using the mod_wsgi interface to Django.

I only want to serve one site, from one Docker container. The apache docs describe a virtual host (link in reply) as the “practice of running more than one web site (such as company1.example.com and company2.example.com) on a single machine”. I’m not trying to do that. But neither am I trying to run two servers.

WSGI

The Django docs for modwsgi (link in reply) tell me to “add the following options”:

  WSGIDaemonProcess example.com python-home=/path/to/venv python-path=/path/to/mysite.com
  WSGIProcessGroup example.com

They don’t say where to add them. I’m guessing /etc/apache2/apache2.conf? (My Docker image is based on Debian, and Debian keeps Apache config files at /etc/apache2.)

The docs also don’t define the meaning of python-home. I’m not using a virtualenv. Should I therefore just ignore that argument?

Last, I don’t have a domain yet. I’m just trying to run and view the site on my local machine for now. So what do I replace example.com with above?

Links missing from the OP:

The Django docs for using Apache and modwsgi

The apache docs describing a virtual host.

Ahh, deployment is so much fun…

In the first link you posted (“Django docs…”), down in the section Using mod_wsgi daemon mode, there’s a link to an external site for the mod_wsgi quick configuration guide. You may find it extremely helpful to review that. In particular, I strongly encourage you to try a sample deployment using their trivial example program simply to verify that all the pieces work as expected without getting mixed up with details involved in actually getting your application to work.

Don’t get hung up on their use of the term “virtual host”. It’s really just an assignment of a name to match to the incoming request. For example, if your domain is example.com, you could set up your virtual host to be www.example.com. This means that requests for something like zzz.example.com would not be processed by your application. The example in the link provided uses *:80, meaning Apache will process all incoming requests regardless of the name in the request.
My understand of the WSGIDaemonProcess directive is that the name parameter is just a name - it’s a way to segregate multiple instances throughout your system. So it appears to me that you’d be perfectly ok to continue using example.com.
The python-home and python-path parameters are documented within the WSGIDaemonProcess page referenced above.

I wouldn’t make these kinds of additions to the base apache2.conf file. I’m fairly certain that in the base file, you’ll see an include directive to include files within the “sites-enabled” directory. The “standard” way of configuring applications within Apache is to separate the individual applications / virtual host configurations into individual files within the /etc/apache2/sites-available directory, then creating a symbolic link to that file from the /etc/apache2/sites-enabled directory. (If you only have one virtual host on one port, then you’re likely to need only one file in that directory.)

Regarding the static files, I have always deployed them to a separate directory using collectstatic, configured within apache to be handled by apache rather than by the application. I’ve seen others recommend using the “whitenoise” package.

Regarding the dynamic creation of static files, my understanding is that you’re going to want to store them outside your docker container. I believe there are a fair number of situations that if your container is restarted, you can lose files created after the original container was created. (I don’t understand docker well enough to know all the situations in which that can occur. I can say that I’ve had that happen to me.)

Best I can say at this point is that I know this all works when done right…

Hope this helps.
Ken

1 Like

Thanks for helping!

On your advice I started a standalone project to get wsgi working. I appended the following to the end of my apache.conf file:

DocumentRoot /mnt/wsgi-jbb/content
<Directory /mnt/wsgi-jbb/content>
  Require all granted
</Directory>

WSGIScriptAlias /myapp /mnt/wsgi-jbb/app.py
<Directory /mnt/wsgi-jbb>
  Require all granted
</Directory>

When I try to visit any of the following links:

http://localhost:8000/myapp/hello
http://localhost:8000/hello
http://localhost:8000/myapp/content/hello
http://localhost:8000/content/hello

I get the following:

Forbidden
You don't have permission to access this resource.

Apache/2.4.38 (Debian) Server at localhost Port 8000

Which means I’m reaching Apache, at least!

When I look up that error I find what might be solution but it describes doing something not mentioned in the WSGI tutorial, which I don’t understand:

sudo adduser <username> www-data
sudo chown -R www-data:www-data /var/www
sudo chmod -R g+rw /var/www
# Who is username? I'm running the Docker container as root.
# What is www-data? Does it apply if I'm serving locally?

Lots of questions - let’s see how many of them I can cover:

That form of the adduser command adds a user to an existing group. If you look at your apache.conf file, you’ll find User and Group directives. Yes, you’re starting the container as root, but for security reasons, Apache changes itself to a restricted user to run. By default on debian, the typical user name and group is “www-data”.

So where this gets confusing / potentially difficult is that in the cases I’m familiar with, the user names within the docker container need to match UIDs on the host. The names don’t need to match if your container has its own passwd / group files. However, access rights are physically granted to the UID/GIDs not the names.

What does all this mean? What it really boils down to is that you need to know the UID / GID on the host that you’ll be using for your apache instance. You’ll set your apache.conf file to use that UID/GID, and make sure that all files being accessed by apache are owned (either by user or group) by that UID (or GID).

wsgi works! This is probably overkill, but what I’m doing is first, as root, set permissions:

find / -iname "*apache*" -exec chmod 755 -R {} \;
adduser www-data www-data
chown -R www-data:www-data /var/www
chmod -R g+rw /var/www

That, plu the apache.conf modifications I described above, are enough to allow me to then run wsgi as appuser instead of root. (appuser in the Docker container has the same uid as myself outside of Docker.)

Next week I’ll come back to Apache. Thanks again!

Short question

Now my puzzle is how to run Apache and Django at the same time. Should I run them on different ports? Or should one of them be starting the other for me?

Some relevant console feedback

I can start the Django server:

appuser@127:/mnt$ python manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).
October 20, 2020 - 19:58:27
Django version 3.1.1, using settings 'ebdjango.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

Or I can start the Apache one:

appuser@127:/mnt$ service apache2 start
[....] Starting Apache httpd web server: apache2AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1. Set the 'ServerName' directive globally to suppress this message
. ok 

But if Apache is already running, Django complains that the port is not free:

appuser@127:/mnt$ python manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).
October 20, 2020 - 19:57:19
Django version 3.1.1, using settings 'ebdjango.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.
Error: That port is already in use.

And if Django is already running, starting Apache gives me a similar error:

appuser@127:/etc/apache2$ service apache2 start
[....] Starting Apache httpd web server: apache2AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1. Set the 'ServerName' directive globally to suppress this message
(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:8000
no listening sockets available, shutting down
AH00015: Unable to open logs
Action 'start' failed.
The Apache error log may have more information.
. ok 

No, when using Apache and mod_wsgi, you do not run Django directly using runserver. If you read the docs for runserver, you’ll see that it’s very explicitly stated that runserver is not a production-quality server - it’s used for development and testing only.

Your WSGI configuration sets up the production-mode execution environment. There’s nothing else needed from that angle.

Awesome, thanks. Sorry, I had read that; it’s just a lot to take in. Now I’m only starting Apache, not the toy Django server.

Apache starts fine, and I can visit static files, but I can’t visit my app. When I try to visit my “polls” app from the Django tutorial by visiting /polls under localhost, I get a permissions error:

Forbidden
You don't have permission to access this resource.

Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 8000

I don’t know whether it’s reading that address as the app or as the folder at site/polls/static/polls. I’m not sure about my wsgi.py file. My only change was to add the line import polls.

More of my code, if relevant

Here’s the root of my site, which gets mounted to /mnt in the Docker container.

I can only include two links per post so I’ll put the rest in the next comment.

The forum won’t let me post any more links to Github!

But anyway, relative to the root of the site that I linked above, my changes to the Apache config can be found at the bottom of ./apache/apache2.conf, and the static data folder is at ./polls/static.

You’ve mapped your application to the /myapp path, so the url would be /myapp/polls, not just /polls.

Woohoo! Now it works! Thank you so much, Ken! I owe you a beer or a cake or something.

My working code, should any future reader want to refer to it

Again, I’m not allowed to post the links, so instead I’ll describe: it’s at the JeffreyBenjaminBrown/learning-aws repo on Github, revision number 5e7acafc661fb3396af47d97510b0690eb26465a, in the python-web-app/ folder.

It usese the Docker container created by revision 47ab68558d115db6951af9dfd91db8292e6c9771 of the ofiscal/tax.co repo on Github, under the docker/ folder.

How I got there

This story seems unlikely to be useful to anybody in the future but whatever, I already wrote it up.

Once I changed the URL (Ken was right, of course) to appuser/polls instead of polls, I got an Internal Server Error. I investigated and found that Apache was using Python 2.7 whereas my code was using Python 3.7, which is where Django was installed. After changing the line apt get libapache2-mod-wsgi to apt get libapache2-mod-wsgi-py3 in the Docker container, it could find Django but still not my code. After fumbling around a while I changed the following Apache configuration directives:

WSGIDaemonProcess
WSGIProcessGroup 
WSGIScriptAlias

to something that works. I get a “you can’t post that link” error when I write the commands out, so you’ll have to refer to my apache2.conf file from the first repo I posted.

1 Like