Publishing a website with IPFS
What is IPFS?
The Inter-Planetary File System is a solution for a permanent, robust, decentralized and uncensorable web. As of this writing it is under active development; and their team recently made snapshots of the Turkish, Kurdish and Arabic versions of Wikipedia to combat the Turkish government’s censorship of Wikipedia.
There are yet many features on the roadmap for IPFS, including native encryption of content, support for network topologies such as Tor, or messaging. For now though, it is already very good at hosting static content, which is what I will show you how to do now.
What does it mean for my website to be on IPFS?
If you can put your website on IPFS (any sort of mutability makes it a challenge, static websites like this one, though, are trivial), then it means it can very easily get decentralized: anyone (or you, on other servers) can “pin” the content of your website, that is, host and distribute a partial or complete copy of it. As long as some node holds a copy of your website, it will be available! For example, if you like this website and you have the IPFS tool installed (more on that later), you could simply run
ipfs pin add /ipns/nullreference.ch and hold a copy you, or anyone connected to you through IPFS can access.
Be careful what you publish on IPFS, though: since only one copy on the IPFS network is required for any content to be served, once someone pins your content you cannot take it down. Do not publish sensitive data on IPFS unless you use and trust encryption.
Installing IPFS on a server
Warning! Being under heavy development, IPFS may have security issues. I heavily suggest you run IPFS in some sandboxed environment such as a Docker container, for example.
I could actually write a tutorial about the installation process for IPFS, but I fear it would be soon deprecated by new developments in the upstream project. For that reason I will just direct you to the official GitHub repository whose description is detailed enough and will be kept up to date.
Publishing your website
I will assume you just installed go-ipfs. If this is the case, the first step is to run
ipfs init which will create an object store, and default configurations. You will normally find those under
~/.ipfs/. Go ahead and take a look! The default configuration should suit our needs for now, though.
First step to publish content is to start the daemon. It will be responsible for interfacing you with the rest of the network. As far as I know the daemon itself cannot fork to the background, so if that is something you need you’ll have to run it in the background by yourself. You can start the daemon by running
IPFS lets you easily add any file, and even directories to the network. This can be done with the
ipfs add <file> which adds files to your local node. Use the
--recursive option to add directories. Note that the files you add now only be present on your node unless someone decides to pin it. Until then bringing your node down brings those objects down with it, and you can still remove your files from the network. You cannot rely on that, though: there is never a guarantee noone has pinned your objects. When I want to publish a version of this website, I go to its root and run the following:
nullreference.ch/ $ ipfs add --recursive ./
added QmQx7tH8uPje88YffkBJqKqEf6WCUAb3jveDs2WB9CZByw nullreference.ch/assets/pubkey.txt
added QmQLiauYThX3nBvVRP4hjAXJXXPcm2uzGxmUxg9BTEfqP5 nullreference.ch/index.html
added QmYATFwSe3X5RyxKtfNRNQsqGRtwtQRUzkEexZEEXjofDK nullreference.ch/reversed-subscripting.html
added QmRXvugNR2RAaUkzKtdodAYMnSbfMLmgPS1oLBARF76Auy nullreference.ch/style/common-dark.css
added QmfTxAmiEcwEkVznUpeA7eAJkkpDbZXNq31nwZMKAH6H7h nullreference.ch/style/common-light.css
added Qmeqb9CUq51fopbcZjdLKLR25xnWyQZc9v4hbkUYbn3EcP nullreference.ch/style/fonts/crimsontext-bold.ttf
added QmPQR1FaDrbM79Nob241rSscAgWvpTmDrAKNMdz6d3tnep nullreference.ch/style/fonts/crimsontext-bolditalic.ttf
added QmYS6hzZSh6hsfiFUntU5xFMbfF9qu81XWnVTDvz7G4EnE nullreference.ch/style/fonts/crimsontext-italic.ttf
added Qmbxs9Qntm9zGgcJvQbCbQuwNA6NEeBBE7zDvKKcVj9aUL nullreference.ch/style/fonts/crimsontext-sspanibold.ttf
added QmcHybGUG9UvweR3PHDnxaMSu4tWMrGQLrJxWCDD6JjgeU nullreference.ch/style/fonts/crimsontext-sspanibolditalic.ttf
added QmULRmTQSJo5t1uACEBz89ebpdR1a3K1xqqspUo7S1c3z6 nullreference.ch/style/fonts/crimsontext.ttf
added QmPPf2WkfCvd6i6M15UehWrsK8J6HasFjjmNRvJsW3Fhzz nullreference.ch/style/fonts/opensans-bold.ttf
added QmaAWqdKEHw1W7R4EGXxVXKJu1e7eJBbib5oNdfSN6rVjc nullreference.ch/style/fonts/opensans-bolditalic.ttf
added QmWYEXEiL73M7rzr6SWKawiNoYWZmD8v9FBfPC2zXMiKuZ nullreference.ch/style/fonts/opensans-extrabold.ttf
added QmNvrAX7MdpogVxgE5SJd3qEKWk1UAKbaQx69RoL3RWFEP nullreference.ch/style/fonts/opensans-extrabolditalic.ttf
added QmdFLdNiTDGmU1Q61YUc68s7H9QW9qLtTAcZCoaNfsyELA nullreference.ch/style/fonts/opensans-italic.ttf
added QmVg81Ju4eeKJxneJdScQ1LbraQ1mXiDD9pGBqeihyC1wn nullreference.ch/style/fonts/opensans-light.ttf
added Qme9RmvTWv2jyYFYYkJJKqW2exvTmXmr44NcEYLTMTmm7t nullreference.ch/style/fonts/opensans-sspanibold.ttf
added Qmd8EEsJzKo7EqYnaDKTyhF1CSrR6bfNCVHgWyJQCTRaY2 nullreference.ch/style/fonts/opensans-sspanibolditalic.ttf
added QmP1B8KmrWVRkGaTf1xpGuEp9mpBvU1PWoE22trPPNNjH4 nullreference.ch/style/fonts/opensans.ttf
added QmXbycN51KFprcn2fWmoWM7QAP9wcNFqwKrpe8QmyFr2Tw nullreference.ch/style/fonts/opensanslight-italic.ttf
added QmTQYMNmWQYUE4tdHTcTY6KtFhuPtqp7SaVgTbBQfhbDw4 nullreference.ch/style/head_bg.jpg
added QmSaUetfxig7vKWwaPF2x6rKHGnZeacCEsLDGo5MDd5YJ1 nullreference.ch/assets
added QmUbopB96DJQyzQxTmwSf2U87kjp5ZHUZptCARzi3qWfhD nullreference.ch/style/fonts
added QmQtHypvFHWZz8Xcos8WEwBKMEtUjRe59s1bEtPm4W7Mos nullreference.ch/style
added QmPTdY7tZpnWnJxhH3QDwHAiGNJaYMq7U3T2iSmxGm27YU nullreference.ch
What we are interested in is that last line which contains the hash of the root of your website. In IPFS every object has a hash, which acts as an address for that object. This is why IPFS is a content-addressable store: the address of an object is derived from its content. Were I to modify any of this content, the address would end up different. Also it is very easy for a client to verify an object matches its address, so you can always be sure what you received from IPFS is what you were supposed to receive: there can be no corruption, intentional or not.
If you have the daemon running, you can try to access
http://localhost:8080/ipfs/<YOUR ROOT HASH>/; you should see your website. Otherwise you can try with my current root hash:
QmPTdY7tZpnWnJxhH3QDwHAiGNJaYMq7U3T2iSmxGm27YU; you should end up on this website as it looks like while I am writing this (unless there is no node to provide those objects, which I will try to avoid). By default the IPFS daemon runs an HTTP gateway on localhost port 8080, but there are also online gateways, such as IPFS’ official website: if you replace
https://ipfs.io you should still end up on your content: the official gateway fetched the content from your node and sent it back to you through HTTP!
Now your content is actually published on IPFS. Congratulations! However it is only accessible through that awful hash no one could possibly remember. Not a great way to attract visitors. Also every time you will make a change to your website, that hash will change and unless you can give everyone that new hash each time, your visitors will be stuck on a single version. Completely unrealistic. For this reason, IPFS comes accompanied by The Inter-Planetary Name System which will let you have a domain name point to an IPFS object, in a mutable way!
Giving your web site a human-useable name
To create a name mutably pointing to an object, simply run
ipfs name publish <YOUR ROOT HASH>.
~/ $ ipfs name publish QmPTdY7tZpnWnJxhH3QDwHAiGNJaYMq7U3T2iSmxGm27YU
Published to QmQArKLQkH76TFCA6iEs9PN2RAt5v1VwuozqYdE1BiUzgo: /ipfs/QmPTdY7tZpnWnJxhH3QDwHAiGNJaYMq7U3T2iSmxGm27YU
Everytime this command is run on another object, it publishes it to the same address: for example
/ipns/QmQArKLQkH76TFCA6iEs9PN2RAt5v1VwuozqYdE1BiUzgo will always point to the latest root of this website as long as I update each at each change. This name is generated from the keypair named
self that was generated for your node when you ran
ipfs init (keypairs can be managed with
ipfs key). Doing this from another node or with another key will yield a different name. This solves the problem of being able to change the content of your website without having to redistribute new hashes each time you change a thing. However this name is no better than an object hash in terms of legibility.
In order to give a human-useable name to your IPFS content, you need to own a domain name. If you don’t have one you are stuck with using the IPNS hash. However if you do, using your domain name for your IPFS content is as simple as adding a
TXT to your DNS zone. IPFS needs you to add a
TXT record with the value
"dnslink=<YOUR IPNS HASH>". Note that using this method you can point to either a IPNS hash or an IPFS object hash; for that reason you must not omit the
/ipfs/ prefix of the hash. For example the record for this website looks like this:
nullreference.ch. IN TXT "dnslink=/ipns/QmQArKLQkH76TFCA6iEs9PN2RAt5v1VwuozqYdE1BiUzgo"
With this done, any place that accepts an IPNS name, including IPFS gateways, will let you use
/ipns/<YOUR DOMAIN NAME> to fetch your content. You can try it now with this website using your local gateway or the official gateway.
Where to go from now?
With that you should be able to publish any static website to IPFS for resistance to censorship, network instability, or dead links (for many reasons, decentralized content-addressable storage is a very good way to store data in a way resistant to time). However in many cases static will not cut it and interactivity is needed. In the case of IPFS this is not yet a solved problem: there is no known easy way to convert any centralized service into a decentralized one and decentralization has to be at the core of the design, from the beginning. However some have already made some services such as a text chat or a paste service. Those are possible as browser can run a working JS implementation of IPFS to dynamically interact with the network. We can expect development of fully decentralized services using IPFS to become simpler with time as features such as encryption or pub/sub messaging become available.