Running a webserver

Volume 6, Issue 11; 02 Oct 2022

Not a public one, a local one for testing and development.

These days, lots of web development really requires a web server. The browsers are unwilling to serve up scriptable content off the filesystem, or let web applications access it. Precisely what the constraints are depends on the browser and the operating system, but the bottom line is,On some browsers, on some operating systems, you can disable some of the security features that are preventing scripts from accessing your filesystem. For the love of all things, don’t do that. if you’re developing an application that does any scripting, you want to run a local web server.

For a couple of decades, I ran an Apache webserver as a system service. I configured lots of virtual hosts on it with different ports. So “localhost:8133” would access a local version of “”, “localhost:8134” would access a local version of “”, etc. It worked fine, but by the time there were a couple of dozen ports, it was a little hard to remember them. “Is the local XProc test suite on 8137 or 8138?”

More recently, for projects like the XProc test suite, I have a gradle task that starts a web server in a Docker container for that project. (On more complicated projects, like this weblog, it’s a networked collection of containers.) Because these servers don’t have to run all the time, I can mostly use the same ports for all of them. That would be inconvenient if I needed to work on two of them at the same time, but that hasn’t happened yet.

For really simple cases like, “if I commit this fix, will the index page for look ok?”, a gradle task is overkill. There isn’t even a build script in the branch that serves that content, and I don’t want there to be.

What I realized a while back, was that I could magically incant a temporary webserver into existance. To wit:

docker run -it --rm -p 8125:80 \
       -v`pwd`:/usr/local/apache2/htdocs \

What that says is, start a Docker container running the httpd:2.4 container (an Apache webserver). Serve up the current working directory on port 8125, and automatically discard the container when the process ends. It runs in the foreground, printing the server log messages while it runs. (Exactly how it says that, I leave as an exercise to the reader.)

So, for a bunch of months, every time I’ve wanted an ad hoc web server, I’ve searched back through my shell history, found one of those, tweaked it as necessary and started it. That’s been fine, as far as it goes. (And if that trick is all you take away from this post, “you’re welcome.”) But it’s a little bit annoying. And what do programmers do when something is annoying?

We script it. Enter a little python script, webserver.

What webserver does is provide a slightly less opaque set of options to run an Apache webserver in a Docker container.



and it’ll start a container in the background that serves the current working directory up on port 8125. Run

webserver --port 8130 --root ~/Projects/

and it’ll start a container in the background that serves ~/Projects/ on port 8130.

I decided that clogging up a shell window with the log wasn’t something I actually cared about very often, so I’ve made the default behavior to just leave it quietly running in the background. If you start with the --foreground option, it’ll run in the foreground showing you the log. (Note, however, that hitting Ctrl-C to get your terminal back won’t stop the server.)

To see what servers are running, use the --list option:

webserver --list

That’ll print a list like this one:

Serving /Users/ndw/Projects/ on port 8130 with gifted_nightingale
Serving /Volumes/Projects/python/webserver on port 8125 with magical_ramanujan
Web servers are 2 of 6 containers

The server names are randomly generated by Docker unless you specify the --start option explicitly:

webserver --start ixml --root /Volumes/Projects/nineml/ixml

If you specify a name, that’ll be used. Now the list shows:

Serving /Volumes/Projects/nineml/ixml on port 8126 with ixml
Serving /Users/ndw/Projects/ on port 8130 with gifted_nightingale
Serving /Volumes/Projects/python/webserver on port 8125 with magical_ramanujan
Web servers are 3 of 7 containers

Note that because I didn’t specify a port, webserver picked the “next available” port. Also note that the list option only shows you webservers. I have four other Docker containers running, but those aren’t shown.

(FYI: If you specify a name or port that’s in use by webserver, you’ll get a friendly message about it. If you specify a name in use by some other Docker container or a port used by some other process, you’ll get a much less friendly error message.)

Generally, the names are irrelevant, but you can use them to stop a specific container:

webserver --stop gifted_nightingale

or to show the logs from a container that’s running in the background

webserver --logs ixml

(You can combine the --logs and --foreground options to watch the logs for any running container.)

If you want to just stop all the containers --stop-all is your friend.

And --help will summarize all the options.

Share and enjoy!

P.S. If you want to run some other container, such as Nginx, or if you want the default port to be something other than 8125, you’ll have to edit the script. There are some class variables around line 16 that you can change. It didn’t seem like it was worth the effort to have options for those, or to read them from some sort of configuration file, but let me know if you disagree. Or, you know, submit a pull request!

Please provide your name and email address. Your email address will not be displayed and I won’t spam you, I promise. Your name and a link to your web address, if you provide one, will be displayed.

Your name:

Your email:


Do you comprehend the words on this page? (Please demonstrate that you aren't a mindless, screen-scraping robot.)

What is six minus three?   (e.g. six plus two is 8)

Enter your comment in the box below. You may style your comment with the CommonMark flavor of Markdown.

All comments are moderated. I don’t promise to preserve all of your formatting and I reserve the right to remove comments for any reason.