Use NGINX to Host a Local Site Using Docker
Tags: nginx, docker, development, frontend
Published: 2023-11-16 20:57:27

---
title: Use NGINX to Host a Local Site Using Docker
published: true
date: 2023-11-03 03:25:34 UTC
tags: nginx, docker, development, frontend
canonical_url: https://licoriceworks.com/blog/run-nginx/
---

 ![Use NGINX to Host a Local Site Using Docker](https://licoriceworks.com/blog/content/images/2023/11/0-mjG1YdoT7xPcnznN.jpg)

Sometimes, you can't simply open your `index.html` in Chrome to test your website. You need the features of a real web server like NGINX.

A common case where directly loading `index.html` falls short is trying to serve local files using the Fetch API. You run into Cross-Origin Resource Sharing (CORS) errors. I hit this little bump when creating the example for this [blog post about the ClipBoard API](https://licoriceworks.com/blog/copying-a-binary-image-blob-to-the-clipboard/). In addition to preventing CORS errors, it's more realistic to use a production web server in your development environment.

To get started, you only need to [install Docker](https://docs.docker.com/get-docker/?ref=licoriceworks.com) and run one command.

```
$ docker run -d --name nginx-development -v $(pwd):/usr/share/nginx/html -p 80:80 nginx

```

The above command does a few things. The `-d` flag ensures that you don't get stuck attached to the container. Using the `--name nginx-development` keyword argument gives this container instance a name, so we can reference it later. The `-p 80:80` maps our local machine's port `80` to the container port that NGINX uses to serve HTTP. This port-mapping allows us to go to `http://localhost` to see our development site.

The `-v $(pwd):/usr/share/nginx/html` keyword argument needs a little more explaining. It links our current working directory to a directory _in_ the running container instance. This directory in the container is the default location where NGINX looks for files to serve. If you want to serve a different directory, then you need to change the `--volume` argument. For example, I like to put my `index.html` at `./src/index.html` in my project directory. To adjust for that, my `-v` argument would look like `-v "$(pwd)/src:/usr/share/nginx/html"`.

If you use your browser to navigate to `http://localhost`, you should be able to see your site. But if you make changes to your `index.html` or any other files on your site, refreshing your browser won't show them. To reload the site after you make changes, use the following command.

```
$ docker restart nginx-development

```

It's good that we can reload our changes, but it requires the extra step: running the above `docker restart` command. With the previous workflow – opening up `index.html` directly – we only needed one step: refreshing our browser. Let's fix that 😤

## Writing an Auto-Reload Script

We can write a simple bash script to reload the NGINX container whenever the source files change

To create this script, we need to either

1. Poll the directory every so often and keep track of changes ourselves
2. Have some way to be notified when there is a change

To avoid managing our own state and to keep our bash script simple, let's go with option 2. How can we get these notifications? On Linux, there is a command-line tool called `inotifywait` that relies on the underlying [inotify C API](https://man7.org/linux/man-pages/man7/inotify.7.html?ref=licoriceworks.com). We could use this, but then we would be locked-in to Linux. I develop on Mac OS and Linux, so I'd like to support both – even better if we can support Windows too.

There is another tool called `fswatch` that works on Mac OS, Linux, and Windows. It may not be installed by default on your operating system. On Mac OS, I needed to install `fswatch` using the following command.

```
$ brew install fswatch

```

Now let's see how this tool works. Let's try running the tool and pointing it at our source directory.

```
$ fswatch src

```

My console just hangs with a blinking cursor. Let's see what happens if we modify one of the files in the `src` directory. I'll try editing one of my css files...

```
$ fswatch src
/Users/curtis/git/licorice-works/src/styles/4913
/Users/curtis/git/licorice-works/src/styles/projects.css
/Users/curtis/git/licorice-works/src/styles/projects.css
/Users/curtis/git/licorice-works/src/styles/projects.css~

```

Wow 😲 Almost immediately, `fswatch` gives us the above output with name of the file that I modified.

This is a lot of change notifications for modifying one file though. It looks like we don't want notifications for the `4913` file or for the `.css~` file. These seem to be oddities of how saving files works on Mac OS. Luckily, the `fswatch` tool has some arguments that we can use to filter the change notifications. By default, it includes everything. This behavior makes including exactly the file types we want a little weird. First, we need to use `-e '.*'` to exclude everything. Then we can use `-i 'css$'` to re-include only files that end in `css`. With our new command, we only get notifications for the CSS file that we changed – not those other weird files.

```
$ fswatch -e '.*' -i 'css$' src                     
/Users/curtis/git/licorice-works/src/styles/projects.css
/Users/curtis/git/licorice-works/src/styles/projects.css

```

This is still two notifications though. We only want to reload NGINX once per change. Thankfully, `fswatch` has a `--one-per-batch` flag that we can use to batch the changes together.

```
$ fswatch -o -e '.*' -i 'css$' src
2

```

After adding `-o`, we only get one line of output when we modify a file. Let's use this output to trigger a restart of our Docker container.

```
$ fswatch -o -e '.*' -i 'css$' -i 'html$' -i 'js$' src | \
while read -r line; do docker restart nginx-development; done

```

That command looks more complicated than the commands we've used so far. Let's dig in. First, I added two more `-i` include filters. One to include any updates we make to `.html` files and another to include `.js` files. Then we pipe `|` the output of this command into a while loop. If you're not familiar with pipe, it just sends the output of the first command as input to the second command. The second command that we're piping into is a while loop. The general syntax of a while loop looks like the following.

```
while <condition>;
do
  <command to execute>
done

```

It's a little easier to read when it's not squished onto one line. For the `<condition>` part, we used a command `read -r line`. Whenever there is a line of input to read, this command reads it, stores it in the variable `line`, and returns true. In our script, this means whenever `fswatch` prints out a batch of changes, our loop will execute once. So each time there is a batch of changes, we will execute our restart command which is the same as above `docker restart nginx-development`.

You could get started by running the two commands.

```
$ docker run -d --name nginx-development -v $(pwd):/usr/share/nginx/html -p 80:80 nginx
$ fswatch -o -e '.*' -i 'css$' -i 'html$' -i 'js$' $(pwd) | while read -r line; do docker restart nginx-development; done

```

But this is a lot to remember or to copy-paste each time, so I wrote a script that takes only one argument: your source directory. You can copy this script and easily reuse it for each of your projects.

**nginx-autoreload.sh**

```
#!/bin/bash

CONTAINER_NAME="nginx-development"

# Use the first argument as our source dir.
# Default to current working directory if no argument is supplied.
SOURCE_DIRECTORY=$1
if [! $1]; then
    SOURCE_DIRECTORY="$(pwd)";
    echo "No source directory provided. Defaulting to current working directory: ${SOURCE_DIRECTORY}";
else
    # Get the full path
    SOURCE_DIRECTORY=$(realpath "${SOURCE_DIRECTORY}")
fi

docker rm --force "${CONTAINER_NAME}" &> /dev/null;

docker run -d --name "${CONTAINER_NAME}" -v "${SOURCE_DIRECTORY}":/usr/share/nginx/html -p 80:80 nginx > /dev/null;
if [$? -ne 0]; then
    echo "Failed to initialize container";
    exit 1;
else
    echo "Successfully started container: ${CONTAINER_NAME}";
fi

echo "Waiting for changes..."
fswatch -o -e '.*' -i 'css$' -i 'html$' -i 'js$' "${SOURCE_DIRECTORY}" |
while read -r line; do
    docker restart "${CONTAINER_NAME}" > /dev/null;
    if [$? -eq 0]; then
        echo "Successfully reloaded ${CONTAINER_NAME}";
    else
        echo "Failed realoading ${CONTAINER_NAME}";
    fi
done

```

You can use the script like `$ ./nginx-autoreload.sh <your-source-directory`, and it will take care of setting up the container and watching for changes. _Note: remember to install Docker and fswatch first!_

We did it! We wrote a single script to host and auto-reload your development site with NGINX 🥳