Static HTTP preview server using Docker

This post introduces an easy-to-setup static preview server built on Docker and details the inner workings

Author's image
Tamás Sallai
3 mins

Motivation

A preview server for static files can be used to showcase frontend works and it's an effective way to show and communicate progress. It should be easily updated, password (or at least a secret) protected and should be reachable by the client. For some time I've used AWS S3 for this purpose, as it has a definite advantage that it does not require a running server, but I find the updating tedious and overall inflexible.

Dockerizing the server

My solution is utilizing a Docker container to host an SSHD for file upload along with a Node.js Express server for authentication and content serving. If you want to quick-start your very own preview server, just go to the Github repository and follow the instructions to configure and fire up the container.

The nuts and bolts

The architecture is fairly easy. There is a Dockerfile that ties everything together, copying and installing the necessary files and software, then fires up a Supervisor. That will start up a simple SSHD that is listening on port 22 and is accessible with the keys present in the authorized_keys. The Node.js server's dependencies are installed by Docker, then the Supervisor starts the server.js.

The Express server

This is the place where most of the magic happens. First, we need a session store, which is now configured as a cookie-based one. It has the advantage over server-based ones that there is no need to store application state, hence less overhead. It's configuration is pretty straightforward.

var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');

...

app.use(cookieParser(config.cookieSecret));
app.use(cookieSession({
	keys: ['key1', 'key2']
}));	

The next thing is to check whether the current user is authenticated or not. Fortunately Express is providing an easy way to add filters to the request chain that can tap into the processing.

var auth = function(req, res, next) {
	var user = req.session.user;
	var users = config.users;
	if (!user || !users[user.username] || users[user.username] !== user.password) {
		res.send(handlebars.compile(formTemplate)({url:req.url}));
	}
	return next();
};

...

app.use(auth);

The contents of the login form is extracted to the form.html and is processed with Handlebars for readability. Basically it is a form that POSTs to /login and sends a redirecturl, a username and a password back. The redirecturl is where the server will redirect after a login, so if someone is landing to a sub resource other than index.html, she won't be redirected to the root path.

The login handler is also pretty straightforward. It just stores the username and the password in the session and sends a redirect to the redirecturl.

app.use(bodyParser.urlencoded({ extended: false }));

...

app.post('/login',function(req, res){
	var redirecturl = req.body.redirecturl;
	var user = req.body.username;
	var password = req.body.password;
	req.session.user = {username: user, password: password};
	res.redirect(redirecturl);
});

Note: In case you are wondering why the cookie session needs to be secured when the username and the password is also stored there, you are right. In this particular case, there is no need for secure sessions, but it's a best practice nevertheless.

Last but not least, the static preview directory is needed.

app.use(express.static('preview'));

Conclusion

While the above implementation is pretty straightforward and might seem trivial to experienced web developers, most of the time providing a simple preview is still an unsolved challenge. This project provides an easy way to start one while maintaining the versatility. Using Docker has several advantages:

  • By customizing the ports for each preview, a single server can host many individually configurable projects. Just don't forget to tag and name your Docker images and containers. Also a deploy script might come handy.
  • This setup allows extensive customizability, whether you'll need HTML5 pushstate support or proxying an API, you are in luck.
September 29, 2015
In this article