Decentralized Static Site with Zola, ENS, and IPFS

Hosting a static site on IPFS is pretty straightforward. Using ENS is also pretty straightforward. The question is: how do you keep ENS updated as cheaply as possible, especially when gas prices are really high. Every update to your site would generate a new hash and, therefore, would need a change to your ENS record to continue being pointed at the latest version.

The tools

There is a massive assumption that you are already aware of or using these tools. Here is a quick rundown and links to further reading if you've never heard of any of them.

  • Zola - a lightweight tool for generating a static website, for those of us who want speed but can't keep up with Hugo updates destroying our sites (I'm not bitter)
  • Ethereum Name Service (ENS) - a service that works a little bit like DNS but has additional social and cryptocurrency wallet features
  • InterPlanetary File System (IPFS) - a distributed file system to help the world move away from centralizing data with giant companies

High-level flow

Here is a diagram of what we are trying to achieve. This diagram introduces the InterPlanetary Name Service (IPNS) which acts like DNS or ENS. This is the magic piece of the puzzle that will keep our costs down.

Request flow starting with a user, passing through ENS, then IPNS, and finally to IPFS hash

Zola

The only real configuration you need here is to change the base_url to something that will create a relative route. What you can do is:

cp config.toml config.ipfs.toml

With your new config, update the base_url:

# The URL the site will be built for
base_url = "/"

# ...rest of config

Setting the base_url in this way is a bit of a hack. Take for example the following menu structure later in my configuration file:

[extra]
main_menu_links = [
  { url = "$BASE_URL/", name = "Home" },
  { url = "$BASE_URL/about", name = "About" },
  { url = "$BASE_URL/blog", name = "Blog" },
]

When the website is built, we get links for the menu items that do not work: //, //about, //blog. If your configuration has a similar structure, you can remove $BASE_URL, keeping the /, and you should be good to go.

Create relative paths

This is the process recommended in the IPFS docs but it requires you to have Nodejs installed. At this time, I do not have an alternative to suggest.

npx all-relative

IPNS

This is the magic. The step that, if you forget or bypass, will cost you your life savings in Gwei.

Create a key (optional)

IPNS uses a private key to make sure that the same IPNS hash is created. You want this so that you don't have to update ENS with a new hash. Now, if you expect to be using the same computer all of the time, you can skip this step. If you work from multiple computers, think you will ever format, or want to use a remote pipeline in the future, then you may want to create a private key and store it safely.

Start by generating a new keypair with a name that you will understand later:

ipfs key gen -s 2048 matthewcantelon-ca

You can now export the keypair if you want to save it in a password manager:

ipfs key export matthewcantelon-ca

Create IPNS record

To do this process yourself, you will need to have the command line version of ipfs installed.

IPFS_CID=$(ipfs add --quieter -r /path/to/folder)

# remove --key if you didn't create one earlier
ipfs name publish --key=matthewcantelon-ca "/ipfs/$IPFS_CID"

If you run these two commands, you should see similar looking output:

Published to k51qzi5uqu5dizp3a9ckoxw84z4w3c9mbwxkfjzhnr4ye2fjctui07tzal19ds:
/ipfs/QmWrpv8rRAhvhGxX3Z9Zq3A7cspN6a6KFihkztWm8cbVsg

The process for updating IPNS is exactly the same so you could save this into a script for reuse.

Before moving on, use an IPFS-capable browser to check that IPNS worked correctly. Copy your public keypair and enter ipns://<keypair> into your browser. Using my keypair as an example: ipns://k51qzi5uqu5dizp3a9ckoxw84z4w3c9mbwxkfjzhnr4ye2fjctui07tzal19ds.

ENS

The easiest way to do this is on the ENS website. If you haven't created a record yet, go ahead and start that process now. If you already have a record, navigate to the administrative page.

Either during creation or when updating an existing record, there is a Content field that can be updated with the IPNS hash.

The ENS user interface with an empty content record

You can see that this field takes quite a few possilbe types of content. In this case, we're going to use IPNS. So, set the content record with your IPNS hash:

The ENS user interface with the IPNS hash added

Once your transaction is confirmed, you can test your new name. Here is mine: ipns://mattcan.eth for the capable and https://mattcan.eth.link for other users.

This is so manual!

Completely agree with you. In the future, I am going to explore ways to add this into a pipeline. There are also existing solutions, like Fleek. I have not tried it but it seems pretty decent.