Software in context

Tags


A Minimal Development Server with nginx and Docker

22nd October 2015

A typical challenge when developing static websites, JavaScript libraries, and Single Page Apps, is setting up a development server. Using your browser to navigate to file://path/to/your/index.html may work to a point, but eventually you'll run into cross-origin complaints. The right answer is to have some kind of local http server serving up your projects.

For years I used local instances of Apache, then nginx, but these had a couple of noteworthy disadvantages:

  1. One http server for many local projects == messy config. It was a pain to configure these servers for multi-tenancy of development projects. Slightly different configurations across projects would start to conflict, and eventually grow into a big mess.
  2. The setup wasn't portable. If I needed to work on one of my projects in a new environment, or I got a new computer, I'd have to set up the http server all over again.

For a while I solved these problem by just including a local ExpressJS server in my projects, but this meant that every project had to be node-based. Plus it felt like a bit of overkill for serving static files. But with the advent of Docker, I've got a new setup that I think solves the above problems elegantly. The setup described below has the following advantages:

  1. Each local project has its own nginx http server.
  2. Dev servers for different projects can run simultaneously.
  3. Completely portable (as long as Docker is installed).
  4. Automatically serve all of your project's contents as static files.
  5. Code changes are reflected immediately by the server.
  6. Easily configurable on a per-project basis.

Here we go:

1. Get Docker up and running

I won't reproduce the detailed instructions at docs.docker.com, but I will highly recommend docker-machine, especially if you develop on a Mac. It seems Docker has finally subsumed some of the best tools for running Docker outside of Linux into a single, supported offering. It's worked flawlessly for me so far. I especially like the Kitematic (beta) UI, which lets you visualize containers, inspect their logs, and browse docker hub, among other things:

Kitematic Screenshot

2. Add the required files to your project

To achieve the setup described above, we'll need to add 2 files to the project. I like to keep these in a directory called dev that lives in the project root. Here's the breakdown:

/dev/nginx.conf

# copied from nginx container on 10/12/15

user  nginx;  
worker_processes  1;

error_log  /var/log/nginx/error.log warn;  
pid        /var/run/nginx.pid;

events {  
    worker_connections  1024;
}

http {  
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        off; # disable to avoid caching and volume mount issues
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

This is a customized configuration file for the containerized nginx we'll be running with Docker—more on this in the next step. The only modification to the default nginx.conf (at the time of writing) is the line:

sendfile        off; # disable to avoid caching and volume mount issues  

This disables caching for static files, as per this ServerFault answer. Before disabling caching, I was running into issues where nginx wasn't serving the exact contents of my project. I couldn't even mitigate this by restarting the nginx container, because the nginx cache is a docker volume.

/dev/run.sh

#!/bin/bash
docker stop dev-container-name  
docker rm dev-container-name  
docker run --name dev-container-name -v "$PWD":/usr/share/nginx/html -v "$PWD"/dev/nginx.conf:/etc/nginx/nginx.conf:ro -p 80:80 -d nginx  

This script does 3 things:

  1. Stops a running container named dev-container-name
  2. Removes the stopped container
  3. Starts a new instance of the public nginx Docker image, with your project's directory mounted as nginx's static file directory, respecting the nginx.conf specified above.

You may see error output about the first 2 steps if there is no running container with the name dev-container-name. Perhaps someone can suggest a modification to this script that first checks for the running container. Then, by volume mounting our entire project into /usr/share/nginx/html and by disabling caching, any code changes we make outside the container will be reflected immediately by what nginx serves up! Feel free to customize this file with a more specific container name, and to serve on the desired port.

Note: Be sure to add executable permissions to this file, because we'll be running it from the command line in the next step.

3. Serve your project locally!

To start serving your entire project via the nginx Docker container, just run the following command from your project root:

$ ./dev/run.sh

Note: If you used Docker-machine in step 1, the container won't be serving on 127.0.0.1:80, and you'll want to add a hosts file entry to the IP address of the docker virtual machine in question. This can be determined using docker-machine ip. For example, if the VM is called "default", use:

$ docker-machine ip default

That's it! Just another way Docker is helping me focus on application development.

Caleb Sotelo
AUTHOR

Caleb Sotelo

I'm a Software Engineer and Director of OpenX Labs. I try to write about software in a way that helps people truly understand it. Follow me @calebds.

View Comments