Deploying a static website to Amazon S3 with express stop and mandate.
There are two key points I want to get across in this blog post.
- Dynamic websites are always easier to build than the equivallent static ones
- Automate everything that has to be done more than once
STOP
Dynamic Websites are Easier to Build
There are lots of tools around that help you build static websites. They work on the principle of you putting your source files in one folder and then they generate some ouptut in another folder. They aren’t dynamic so they don’t tend to support things like connecting to a database or making web requests while rendering a page. Many have some form of plugin system (e.g. docpad) which lets you extend their functionality into really complex beasts.
All these systems are arguably also dynamic websites though. Most even come with a built in server so you can see your edits live rather than having to manually re-compile. What this means is that you can use any static site generator as it it’s dynamic. Using a dynamic website generator gives you many more options though. Dynamic website libraries like express get a lot more users and have carefully crafted APIs, whereas the purely static site generators almost always have a tiny handful of users and poorly thought out APIs.
Static Websites are Easier to Host
Amazon S3 means that it’s now possible to host websites with vast amounts of traffic for a few cents per month. It also offers fantastic performance and uptime. This means that static websites are almost always the best way to go if you don’t have any content that needs to be dynamic (or if your dynamic content can be provided from systems like Disqus).
Conclusion
I’ve created stop which is a small command line application that downloads an entire website into a static folder. It does this by taking a starting point you provide and then parsing any HTML it downloads to find links to follow. This lets you develop and test a dynamic website, before downloading it into a local, static folder.
Stop also does a few other helpful things on the side. If you pass the minify-css
and minify-js
options it will minify CSS and JavaScript respectively. Minifying as you download the site and make it static is a really clean and simple way to do this and saves you cluttering up your application logic with minification.
Stop can take a URL, a port number or a JavaScript file (that uses node.js to launch a website) as its source. If you add a .stop.toml
file in the root of your project with the following contents then all you will need to do to make your static site is type stop
in a command line.
source="./server.js"
destination="./out"
[options]
minify-js=true
minify-css=true
MANDATE
Having got our static website in the out
folder, we need to upload it to Amazon S3. If there are a few files and it’s a one off you can do that through the AWS Management Console. I like to keep everything automated though, so I use mandate. To use mandate you just have to create a file in the root of your project called .mandate.toml
with content that looks something like:
source="./out"
[aws]
bucket="htmlparser.forbeslindesay.co.uk"
key="<YOUR AWS KEY HERE>"
secret="<YOUR AWS SECRET HERE>"
This takes everything from the source folder and uploads it to the output bucket. If you use IAM from Amazon you can create an individual user just for that one bucket by giving it a user policy of:
{
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::htmlparser.forbeslindesay.co.uk",
"arn:aws:s3:::htmlparser.forbeslindesay.co.uk/*"
]
}
]
}
where htmlparser.forbeslindesay.co.uk
is replaced with your bucket name.
Having done all that, you can deploy a new version of your static website just by typing
stop
mandate
The Final 10%
We now have a very nearly fully automated system. The only additional thing that I like to do is version my static assets so that I can set very long cache times on them. What that means is that everything except the html files will have a url like example.com/static/0.0.0/foo.js
. That won’t work if I upload two versions with the same version number. As such I have to make sure the version is updated each time. I use versionify for this, which prompts me to update if I haven’t already.
In order to avoid uploading every version of every static asset every time I deploy, I like to delete the output folder after I’m done. If I was only working on unix based OSes I could just use rm -rf
but that doesn’t work on windows, so I use rimraf which does the same thing using node.js to be cross platform.
Pulling that all together, each release should run:
obliterate out
versionify
stop
mandate
obliterate out
Clearly I don’t want to type that every time I do a release, so instead I use npm’s scripts feature to automate this:
package.json:
{
"name": "htmlparser",
"version": "1.0.0",
"versionify": "1.0.0",
"private": true,
"dependencies": {},
"devDependencies": {
"stop": "~2.1.1",
"rimraf": "~2.2.0",
"mandate": "~0.1.1",
"versionify": "~1.0.1"
},
"scripts": {
"prerelease": "versionify && rimraf out",
"release": "stop && mandate",
"postrelease": "rimraf out"
}
}
Then I can install everything using npm install
and release a new version just by typing npm run release
(and it will prompt me to update the version automatically.)