Sometimes, you can't simply open your in Chrome to test your website. You need the features of a real web server like NGINX. index.html A common case where directly loading 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 . In addition to preventing CORS errors, it's more realistic to use a production web server in your development environment. index.html blog post about the ClipBoard API To get started, you only need to and run one command. install Docker $ docker run -d --name nginx-development -v $(pwd):/usr/share/nginx/html -p 80:80 nginx The above command does a few things. The flag ensures that you don't get stuck attached to the container. Using the keyword argument gives this container instance a name, so we can reference it later. The maps our local machine's port to the container port that NGINX uses to serve HTTP. This port-mapping allows us to go to to see our development site. -d --name nginx-development -p 80:80 80 http://localhost The keyword argument needs a little more explaining. It links our current working directory to a directory 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 argument. For example, I like to put my at in my project directory. To adjust for that, my argument would look like . -v $(pwd):/usr/share/nginx/html in --volume index.html ./src/index.html -v -v "$(pwd)/src:/usr/share/nginx/html" If you use your browser to navigate to , you should be able to see your site. But if you make changes to your 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. http://localhost index.html $ docker restart nginx-development It's good that we can reload our changes, but it requires the extra step: running the above command. With the previous workflow – opening up directly – we only needed one step: refreshing our browser. Let's fix that 😤 docker restart index.html Writing an Auto-Reload Script To create this script, we need to either Poll the directory every so often and keep track of changes ourselves 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 that relies on the underlying . 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. inotifywait inotify C API There is another tool called 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 using the following command. fswatch fswatch $ 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 directory. I'll try editing one of my css files... src $ 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, gives us the above output with name of the file that I modified. fswatch This is a lot of change notifications for modifying one file though. It looks like we don't want notifications for the file or for the file. These seem to be oddities of how saving files works on Mac OS. Luckily, the 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 to exclude everything. Then we can use to re-include only files that end in . With our new command, we only get notifications for the CSS file that we changed – not those other weird files. 4913 .css~ fswatch -e '.*' -i 'css$' css $ 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, has a flag that we can use to batch the changes together. fswatch --one-per-batch $ fswatch -o -e '.*' -i 'css$' src 2 After adding , 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. -o $ 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 include filters. One to include any updates we make to files and another to include 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. -i .html .js | while <condition>; do <command to execute> done It's a little easier to read when it's not squished onto one line. For the part, we used a command . Whenever there is a line of input to read, this command reads it, stores it in the variable , and returns true. In our script, this means whenever 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 . <condition> read -r line line fswatch 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 , and it will take care of setting up the container and watching for changes. $ ./nginx-autoreload.sh <your-source-directory 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 🥳 Also published . here