24 Feb 2017
Adding Isso Comments to Hugo
“Featuring: Hugo, Isso, Caddy & Ubuntu”


If you’ve read my recent post on Disqus’s underhand link hijacking, you’ll know that it gave me the impetus I needed to finally make good on my long-held intention of getting rid of Disqus and installing a self-hosted comments system on Them’s Good Broth instead—to wit Isso.

When I look back now at the amount of 'stuff' I had to do, to get Isso working, it’s not really that complex. However, it did take me several hours of brow-furrowing, head-scratching and nose-picking to make head’n’tail of Isso’s documentation and work out how to shoe-horn that into my Hugo setup. So, I thought I’d write it all down here, while it was relatively fresh in my mind. Hopefully it might help a few other folk out too.

I’ve tried to pitch it at the level of someone who knows the basics of how to navigate round a file-system, using the command-line and who can use a text-editor. I’ve also assumed your site is running on a Ubuntu server [although any flavour of Linux should be similar] that you have admin or sudo access to.

Writing this all down, it turned into a bit of a mammoth post. So there are doubtless typos aplenty and possibly even errors, but hopefully nothing that will cause the entire intarwebs to explode. If you spot any such waywardness before my post-publication proof-reading brings it to ground, please let me know.

OK? Ready? Let’s go!

The first few stages involve getting Isso up and running on your server. So, login to that.

[server] Setup Python Virtual Environment

Smarter people than me recommend that you should always setup a Virtual Environment when working with a Python project [such as Isso].

With a Virtual Environment, you’re basically creating a local copy, within your project folder of; Python and any Python modules your project needs, along with any other dependencies—as opposed to running your project via the system-wide Python. The thinking behind this being that, your project will be self-contained and not liable to come crashing down round your ears, if a future update to Python [or some other part of your system] breaks something.

The most popular and well-established tool for creating a Python Virtual Environment is the eponymous Virtualenv but, being a thrills junkie, I opted to go for a newer kid on the block; Pipenv.

You install Pipenv via Pip* [or, in my case Pip3]:

$: sudo -H pip3 install pipenv

*[Pip3 is included with Python3, but Pip needs to be installed separately in Python2]

I used sudo to install pipenv system-wide, so I can access it from anywhere. The -H flag is required to stop Pip complaining about the ownership of your ~/.cache directory, when you install using sudo. If all goes well, you should see a long screed of output, finishing with the line:

Successfully installed pipenv-3.4.2

If, instead, you get a load of red error text, it’s most likely to be permissions errors, in which case you probably forgot sudo or the -H flag.

Once Pipenv is installed, you’re ready to use it to create your first Virtual Environment. But first, a slight caveat:

The aforementioned Virtualenv installs its local copies of Python et al into an env folder inside your project folder. By default Pipenv creates its .venv folders [note different spelling] inside your ~/.cache directory. That seems daft to me. The whole point of using a Virtual Environment is to keep everything together and self-contained. But, luckily, Pipenv has an option whereby you can set an environment variable in your shell, to tell Pipenv to create its .venv inside your project folder.

If you want to do this add the following line to your ~/.zshrc or ~/.bash_profile [depending on which shell you use]:

#tell pipenv to create venvs inside project folders

Remember to source ~/.zshrc [or source ~/.bash_profile] afterwards, to load the new configuration into your shell.

[server] Activate Python Virtual Environment

Still with me? If so, it’s now time to create a Virtual Environment and get this show on the road.

Navigate to whichever directory you want to keep Isso in and create your Virtual Environment there. In this example, I’ll build everything in my home directory:

$: cd ~

~ $: mkdir isso

$: cd isso

isso $: pipenv --three  #tells pipenv to create a Python3 virtual environment

If all goes according to plan, you should see output something like this:

Creating a Pipfile for this project...
Creating a virtualenv for this project...
⠋Already using interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /home/yourname/isso/.venv/bin/python3
Also creating executable in /home/yourname/isso/.venv/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.

Virtualenv location: /home/yourname/isso/.venv

…​and if you ls -al your project folder, you should see the new 'stuff' Pipenv has created.

isso $: ls -al

total 16
drwxrwxr-x  3 yourname yourname 4096 Feb 24 19:42 .
drwxr-xr-x 12 yourname yourname 4096 Feb 24 19:46 ..
-rw-rw-r--  1 yourname yourname   68 Feb 24 19:42 Pipfile
drwxrwxr-x  6 yourname yourname 4096 Feb 24 19:42 .venv

The Virtual Environment is setup. Now it’s time to activate it. You do this in Pipenv as follows [make sure you’re still in your project directory]:

isso $: pipenv shell

You should see similar output to this, as your Virtual Environment is activated:

Spawning environment shell (/usr/bin/zsh).
source /home/yourname/isso/.venv/bin/activate
isso  $:  source /home/yourname/isso/.venv/bin/activate
(isso) isso $:

Notice how your command prompt has changed so it begins with the name of your Virtual Environment in brackets. This shows you that you are now working in a Virtual Environment, which you can also confirm with:

(isso) isso $: which python


Notice also how you’re now using the copy of Python installed in the .venv directory, instead of the system version. Now you’re ready to install Isso into this Virtual Environment.

[server] Install Isso

You install Isso using Pip again. Now you’re working in the Virtual Environment, you don’t need to specify python3 or pip3 anymore as, by using the --three flag previously, you’ve set up the Virtual Environment to use these as the default.

(isso) isso $: pip install isso

Collecting isso
  Using cached isso-0.10.6-py2.py3-none-any.whl
Collecting html5lib==0.9999999 (from isso)
Collecting itsdangerous (from isso)
Collecting werkzeug>=0.9; python_version != "2.6" and python_version != "2.7" (from isso)
  Using cached Werkzeug-0.11.15-py2.py3-none-any.whl
Collecting misaka<2.0,>=1.0 (from isso)
Requirement already satisfied: six in ./.venv/lib/python3.5/site-packages (from html5lib==0.9999999->isso)
Installing collected packages: html5lib, itsdangerous, werkzeug, misaka, isso
Successfully installed html5lib-0.9999999 isso-0.10.6 itsdangerous-0.24 misaka-1.0.2 werkzeug-0.11.15

The install should only take a few seconds [Isso ain’t bloatware!]. If you ls -al your project directory again, you may think nothing has happened, as everything looks the same. But Isso has been installed into the .venv/bin/ folder. If you ls -al that, you should see it there, along with the Virtual Environment’s local copies of Python, Pip and a couple of other libraries Isso uses:

(isso) isso $: ls -al .venv/bin

total 4416
drwxrwxr-x 2 yourname yourname    4096 Feb 24 20:00 .
drwxrwxr-x 6 yourname yourname    4096 Feb 24 19:42 ..
-rw-rw-r-- 1 yourname yourname    2093 Feb 24 19:42 activate
-rw-rw-r-- 1 yourname yourname    1035 Feb 24 19:42 activate.csh
-rw-rw-r-- 1 yourname yourname    2233 Feb 24 19:42 activate.fish
-rw-rw-r-- 1 yourname yourname    1137 Feb 24 19:42 activate_this.py
-rwxrwxr-x 1 yourname yourname     251 Feb 24 19:42 easy_install
-rwxrwxr-x 1 yourname yourname     251 Feb 24 19:42 easy_install-3.5
-rwxrwxr-x 1 yourname yourname     225 Feb 24 20:00 isso
-rwxrwxr-x 1 yourname yourname    2562 Feb 24 20:00 misaka
-rwxrwxr-x 1 yourname yourname     223 Feb 24 19:42 pip
-rwxrwxr-x 1 yourname yourname     223 Feb 24 19:42 pip3
-rwxrwxr-x 1 yourname yourname     223 Feb 24 19:42 pip3.5
lrwxrwxrwx 1 yourname yourname       7 Feb 24 19:42 python -> python3
-rwxrwxr-x 1 yourname yourname 4460336 Feb 24 19:42 python3
lrwxrwxrwx 1 yourname yourname       7 Feb 24 19:42 python3.5 -> python3
-rwxrwxr-x 1 yourname yourname    2340 Feb 24 19:42 python-config
-rwxrwxr-x 1 yourname yourname     230 Feb 24 19:42 wheel

That’s Isso installed. Next stage is to configure it.

[server] Configure Isso

Using your text editor of choice, create an isso.cfg file inside your project directory. There are a lot of configuration options you might stick in there but, to get you started, here’s some basics that I used:

; |||||||||||| general settings ||||||||||||||
;database location. automatically created if doesn't already exist
dbpath = /home/yourname/isso/comments.db

;this will be the 'X-Script-Name' we proxy server requests to [later!]
name = isso

; your website or blog [not the location of Isso]
host =

; logfile. might need to create this first
log-file = /home/yourname/isso/isso.log

; get notifications by email
notify = smtp

;||||||||||| server section ||||||||||
;port to listen on. choose a number you like!
listen = http://localhost:8181/

;||||||||| smtp section [for notifications] |||||
;these are the details for the 'from' address Isso uses to send notifications
;you might want to use a dedicated email account for this
username = yourusername
password = yourpassword
host = smtp.yourmailhost.com
port = 587
security = starttls

;this is the 'to' address Isso sends notification emails to
to = you@youremail.com

;from address as shown on emails. should correspond to sender account above
from = "isso comments" <yourusername@yourmailhost.com>
timeout = 10

;|||||| guard –Isso's basic spam protection |||||||
enabled = true
;no. of allowed comments per minute
ratelimit = 2
;no of direct replies allowed
direct-reply = 3
;can people reply to their own comments while edit window still open
reply-to-self = false
;do commenters need to leave a name
require-author = true
;do commenters need to provide an email
require-email = false

;allowed markdown in comments. [uses misaka markdown]
;default options allow most 'unharmful' markdown
options = strikethrough, superscript, autolink
;default allowed = a, blockquote, br, code, del, em, h1, h2, h3, h4, h5, h6, hr, ins, li, ol, p, pre, strong, table, tbody, td, th, thead, ul
;allowed-elements =
;default allowed = align, href

;creates identicons for users across isso installations
;OK to use this salt
salt = Eech7co8Ohloopo9Ol6baimi
algorithm = pbkdf2

;comments must be moderated before publication
;NOTE: requires "notify = smtp" to be set in the [general] section

Once you’ve saved that lot in your isso.cfg file and, seeing as you’ve made it this far, you might as well reward yourself by firing up Isso:

(isso) isso $: isso -c ~/isso/isso.cfg &

the -c [for 'configuration'] flag takes the path to your config file. The & runs Isso in the background, rather than blocking your further use of your terminal.

I must warn you, Isso isn’t exactly the most loquacious piece of software in the world. So, at this stage, you won’t actually see anything that gives you any info as to whether it’s running properly or not. If you’re lucky you’ll just see a process ID echoed to the screen. For example:

[1] 7294

You’ll only get any more voluble output, if there’s a problem.

Since I got a process ID, I’ll assume <something>'s happening. So, let’s kill Isso for now and go and go and retrieve our existing comments from Disqus. [If you’re not importing existing comments, you can ignore the next couple of sections]:

(isso) isso $: kill -SIGINT $(pgrep isso)

[1]  + 7294 done       isso -c ~/isso/isso.cfg

Actually, if you do another ls -al on your project folder now, you should see confirmation that Isso has in fact been running, in the form of the existence of the newly created comments.db comments database file that you specified in isso.cfg.

(isso) isso $: ls -al

total 32
drwxrwxr-x  3 yourname yourname 4096 Feb 24 20:42 .
drwxr-xr-x 12 yourname yourname 4096 Feb 24 21:01 ..
-rw-r--r--  1 yourname yourname 6144 Feb 24 20:42 comments.db <---HERE!
-rw-rw-r--  1 yourname yourname 1879 Feb 24 20:42 isso.cfg
-rw-rw-r--  1 yourname yourname  168 Feb 24 20:53 isso.log
-rw-rw-r--  1 yourname yourname   68 Feb 24 19:42 Pipfile
drwxrwxr-x  6 yourname yourname 4096 Feb 24 19:42 .venv

Next stop Disqus…​

[disqus.com] Export Existing Comments from Disqus as XML

You will, I’m sure, not be surprised to find that Disqus have tucked away in a dusty, cobweb-draped corner of their website, the options for retrieving the XML dump of your site’s comments. But, pioneering devil that I am, I donned my baggiest shorts and pithiest pith helmet—and spent several happy moments scouring their site to find it for you. So, here goes:

First login to your Disqus account at disqus.com and click on 'Admin':

[sorry. No more free incoming links for them, from me!]


Once in the admin section, popup the menu item Your Sites and select the site from which you want to export the comments:


With the appropriate site selcted, click on the Community tab:


You’ll now see a vertical menu down the left-hand side of the screen. Select Export from that menu:


This will take you to another screen, where you click on the Export Comments button:


Don’t get too excited yet, because you’re not getting your grubby mitts on your data, just yet. Disqus will email you out a link to download them. So you’ll have to twiddle your thumbs for a few minutes, til this arrives:


[server] Import Comments XML to Isso

Once your email from Disqus arrives, with the URL for retrieving your comments dump, you can head back to your server again, to import them into Isso. If you’ve logged out of your server, don’t forget you’ll need to cd back to your Isso project directory and run pipenv shell to activate your Virtual Environment again. We need to be here again, at the Pipenv prompt:

(isso) isso $:

Now use your favourite web downloader to grab the content from the link you got in the email from Disqus:

(isso) isso $: wget https://media.disqus.com/uploads/exports/3462/514/yourblogname-YYYY-MM-DDTHH:MM:SS.xxxxxx-all.xml.gz

Resolving media.disqus.com (media.disqus.com)...,
Connecting to media.disqus.com (media.disqus.com)||:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 36507 (36K) [application/octet-stream]
Saving to: ‘yourblogname-YYYY-MM-DDTHH:MM:SS.xxxxxx-all.xml.gz’

yourblogname-YYYY-MM-DDTHH:MM:SS: 100%[=================================================>]  35.65K  --.-KB/s    in 0s

2017-02-24 21:40:39 (199 MB/s) - ‘yourblogname-YYYY-MM-DDTHH:MM:SS.xxxxxx-all.xml.gz’ saved [36507/36507]

Isso needs the file to be uncompressed, in order to import it. So, do that next [Hint: If your shell supports autocomplete, you can just hit tab after typing gunzip and it should automatically fill in that atrociously long filename for you, seeing it’s the only compressed file in your project directory]:

(isso) isso $: gunzip yourblogname-YYYY-MM-DDTHH:MM:SS.xxxxxx-all.xml.gz

Unzipping the file only takes an instant so, again, you can ls -al to make sure it actually did get uncompressed:

(isso) isso $: ls -al

total 292
drwxrwxr-x  3 yourname yourname   4096 Feb 24 21:45 .
drwxr-xr-x 12 yourname yourname   4096 Feb 24 21:48 ..
-rw-r--r--  1 yourname yourname   6144 Feb 24 20:42 comments.db
-rw-rw-r--  1 yourname yourname   1879 Feb 24 20:42 isso.cfg
-rw-rw-r--  1 yourname yourname    168 Feb 24 20:53 isso.log
-rw-rw-r--  1 yourname yourname     68 Feb 24 19:42 Pipfile
-rw-rw-r--  1 yourname yourname 266143 Feb 24 21:11 yourblogname-YYYY-MM-DDTHH:MM:SS.xxxxxx-all.xml
drwxrwxr-x  6 yourname yourname   4096 Feb 24 19:42 .venv

OK. You’ve now got an unzipped XML dump of your Disqus comments. Time to fire up Isso again and import them. Again you pass the path to the isso.cfg file, using the -c flag and also use import followed by the name of your comments XML file.

I’m not sure what the --empty-id flag does but, if I leave it out, I get an error telling me to use it. I suspect it may be just resetting the [empty anyway] comments.db file that was created when you first ran Isso.

(isso) isso $: isso -c ~/isso/isso.cfg  import --empty-id yourblogname-YYYY-MM-DDTHH:MM:SS.xxxxxx-all.xml

[100%]  17 threads, 53 comments

You can try running Isso again, at this point, if you want, but you won’t see anything more than happened last time. Isso is just sitting there, waiting for your webserver to talk to it. So, let’s go and set up proxying on your webserver now, to give Isso someone to converse with.

[server] Configure Caddy Server to Proxy to Isso

Right. I’m afraid I’m going to be targetting a niche audience again here, when it comes to server configuration.

Although I used to run this site on Nginx, I recently switched to Caddy, what with it being all shiny and new and written in Golang. So I’ll be showing you how I’ve set up Caddy to proxy to Isso. The Caddy config syntax isn’t a million miles away from Nginx’s so hopefully, if you’re using Nginx, you can adapt as necessary. If you’re running Apache, you’re probably going to do all this with mod_python anyway and are only reading this to snort derisively at my fumblings.

Here is my Caddyfile [Caddy’s configuration file] for proxying any URLs ending in /isso to the Isso app:

# |||||||||| yoursite.com ||||||||||||||

#redirect www
www.yoursite.com {
    	redir https://yoursite.com{uri}

yoursite.com {

        root /www/yoursite.com/
    	log /var/log/yoursite.com.access.log
    	errors /var/log/yoursite.com.error.log

        #compression on

        #Isso Comments
        proxy /isso localhost:8181 {
        without /isso
        header_upstream X-Script-Name /isso

Now, to be honest, Caddy is all very new to me and I’m not sure exactly what the without /isso and transparent directives do, but they’re what I picked up from my online quest for enlightenment, and it all works. So who am I to question why?

The two most important pieces of the configuration are as follows:

1: Make sure the proxy port in the Caddyfile matches the one you put in the [server] section in Isso’s isso.cfg file


proxy /isso localhost:8181


listen = http://localhost:8181/

2: Make sure the X-Script-Name in your Caddyfile matches the one you put in the [general] section in Isso’s isso.cfg file:


header_upstream X-Script-Name /isso


name = isso

When you’re satisfied that your Caddyfile is correct and those two sections correspond between it and isso.cfg, you can close your Caddyfile and then kill and restart your Caddy server, by cd-ing to the location of your Caddyfile and running the caddy command.

(isso) isso $: cd /www

(isso) www $: kill -SIGINT $(pgrep caddy)

(isso) www $: caddy &

Again, the & ampersand on the end of the command will run the server in the background, so we can do other stuff in the terminal. Later on, you’ll create a systemd service to run both Caddy and Isso, automatically when the server reboots. But this will do for now and for testing purposes.

If Caddy starts up OK, you should see output something like this in your terminal:

[1] 8802
Activating privacy features... done.

Caddy is back up and running and has now been configured to proxy anything under the /isso path back to Isso. Let’s go back to your Isso project folder and see if Isso’s listening properly.

With Isso being the taciturn creature it is, you won’t get any useful info in the terminal to tell you things are running OK, but you can use this slighty roundabout method to check if requests to yoursite.com/isso are being proxied correctly back to Isso:

(isso) www $: cd ~/isso #rem to cd back to your Isso project dir

(isso) isso $: kill -SIGINT $(pgrep isso) #and make sure Isso isn't running

#if you see "kill: not enough arguments"
#don't worry. That just means Isso was already 'dead'!

OK. Now visit yoursite.com/isso in your browser. You should see a 502 Bad Gateway error. This means Caddy is correctly proxying the request for /isso back to Isso, but Isso isn’t waiting at the gate to receive it [do you see what I did there!]. Mainly because Isso is dead. But don’t tell Caddy that. He will be terribly upset.


Back to the terminal and let’s bring Isso back to life:

(isso) isso $: isso -c ~/isso/isso.cfg &

[1] 11893

Now go back to your browser again and reload yoursite.com/isso. This time you should get a different Bad Request error message. This is actually a good sign. It means your server proxied the request back to Isso and Isso received it. But Isso wasn’t able to make head nor tail of what it was receiving. This is because Isso also needs some code to be embedded on your web-pages, in order for it to know which page is which, which comments belong on which page and where to display said comments.


Essentially, though, you’ve now got Isso setup and running and speaking to Caddy. So, for the moment, the server side of things is OK and it’s time to go back to your local development computer and embed the necessary bits’n’bobs of code in your Hugo site pages.

As things are at present, once you close your terminal and/or disconnect from your server, Caddy and Isso will shutdown. So you’ll either need to leave your terminal connected, or restart Caddy and Isso again, when you log back in later.

Don’t worry about the clunkiness of that, for now. As I said before, you’ll create system services later to take care of automatically booting up Caddy and Isso when your server boots and also automatically restarting them, if they crash. For now though, just put up with using this clumsy 'manual On/Off Switch'.

[local] Add Isso Code to Hugo

Back in the comfort of your own development computer, it’s time to add a couple of snippets of code to your Hugo site pages, in order to get Isso working. Here again, be a couple of 'Dragons' set to slay the unwary traveller. But stick with me, kid, and we’ll get through this!

I’m assuming that your Hugo site setup features a header.html partial, saved under /themes/yourthemename/layouts/header.html. That’s where I’m going to add the first Isso snippet. If your sites <header> section is defined elsewhere, such as in one of Hugo’s other template files, adapt accordingly:

Add the following code somewhere in the <header section>:


{{ "<!-- isso -->" | safeHTML }}
<script data-isso="{{ .Site.BaseURL }}isso/" src="{{ .Site.BaseURL }}isso/js/embed.min.js"></script>
{{ "<!-- end isso -->" | safeHTML }}


Also make sure .Site.BaseURL is defined within your Hugo site’s config.toml or config.json file. It should have a trailing slash, eg: BaseURL = "https://yoursite.com/".

That snippet will embed the Javascript necessary for Isso to function in the header of all your pages.

The second snippet is the code that actually displays the comments box and comments on the pages.

I put this snippet in my /themes/yourthemename/layouts/_default/single.html template. So it will only be displayed on pages which are individual blog articles and not on pages which contain lists of posts, such as index or category pages.

Where you put it within the template itself, will depend on the layout of your single.html template, but here’s what you need to put. I’ve added mine at the end of the <footer> section, which I have at the bottom of each article:

{{"<!-- begin comments //-->" | safeHTML}}
<section id="isso-thread">

{{"<!-- end comments //-->" | safeHTML}}

Now, in an ideal world, we’d be done now. However, you’re probably looking uneasily over your shoulder and wondering where these 'Dragons' I spake of earlier are lurking. Well might you ask. Put an ice-pack on your head and read on…​

The code you embedded in the header of your pages, looks straightforward enough:

<script data-isso="{{ .Site.BaseURL }}isso/" src="{{ .Site.BaseURL }}isso/js/embed.min.js"></script>

So, obviously your comments are going to be served by an Isso-related Javascript file located at http://yoursite.com/isso/js/embed.min.js. Simples, eh? Although, on pondering this, you might suddenly think,

"But. Wait. Where do I get this Javascript file from? Do I download it from somewhere?"

Congratulations! You’ve just encountered your first dragon!

[local] Fool Hugo into Displaying Isso Comments

You see, the Javascript file at http://yoursite.com/isso/js/embed.min.js doesn’t actually exist. The directory isso/js doesn’t actually exist either. Those are just the routes which the Isso app uses internally to call the appropriate function to embed the Javascript on your page.

If that didn’t make any sense, consider the following analogy.

A friend [visitor to your site] asks you [Caddy server] to go round to his mum’s house and ask her [Isso] for the pair of jeans lying under his bed.

Unknown to both of you, his mum has washed & ironed the jeans and put them away in his wardrobe. However, when you ask for them she knows what you’re after and goes and fetches them.

You take the jeans back to your mate and he is happy. He asked for 'Jeans under the bed' and got returned a pair of jeans. The fact that no jeans existed under the bed and that his mum got them from somewhere else is beside the point.

The same is happening when your server requests the Javascript at http://yoursite.com/isso/js/embed.min.js. No file actually exists there but, when asked for that file, Isso sends back a chunk of Javascript and everybody’s happy.

Looney McLoonster

Er…​ OK. Maybe I should leave off the analogies in future and stick to incomprehensible jargon.

Anyway, the upshot of this is that Isso is not going to work in your development environment because you’ve not got the actual Isso app installed on your development machine, ready to spew out Javascript on demand. Even if you did have Isso installed, Hugo’s built-in server [which you use when previewing your site] is very basic and can’t do proxying, so there would be no way Hugo could communicate with Isso, anyway.

Try it, if you want. Rebuild your Hugo site:

hugo server --watch --renderToDisk

You’ll see that there’s nothing visible on the individual post pages, where you added the <section id="isso-thread"> </section> code and, if you try and visit the URL where the Javscript file is supposed to live, you’ll get a 404 Not Found error because [all together now!…​] It doesn’t actually exist.


Don’t fret though. I have a sneaky workaround that can save the day:

As explained in that spellbinding analogy earlier; in order for the comments to display properly, Hugo Server wants to receive some Javascript, when a request is made for http://localhost:1313/isso/js/embed.min.js. Like your jeans-wearing mate, it doesn’t really matter how that Javascript comes to be received. As long as Hugo Server gets the Isso Javascript returned, when it asks for that URL, it’ll be happy.

So, because you’ve not got Isso running and thus there’s no Javascript being sent back in response to requests for that URL, you’re going to have to trick Hugo Server, by actually placing [temporarily] a real embed.min.js file in that location.

Make the following directory structure inside your Hugo site: /public/isso/js/ [the /public folder already exists. Add the others inside it].

Inside that you need to place an embed.min.js file, which contains the actual Javascript that Isso would generate, if it were running. You can download a copy of embed.min.js form the following link:

ASIDE 01: This dummy embed.min.js file has been 'unminified' so as not to crash Atom, which has a long-standing bug, when trying to read files with extra-long lines of text.

ASIDE 02: you may be wondering why the Hell Isso makes things so complicated, by generating its Javascript on the fly like this, rather than just letting you download the actual source? Supposedly this is so that, when the Isso app itself is updated, the Javascript embedded on your site automatically updates too and you don’t have to manually replace 'actual' Javascript files. Good idea but, as we’ve seen, it don’t half complicate things!

This is how your Hugo site structure should look, with your dummy embed.min.js file in place:

Create your dummy embed.min.js file here

If you let Hugo rebuild your site, you should find that you now have a comments box on your pages! You can now tweak its layout and styling to your wee heart’s content.


remember to move isso/js/embed.min.js out of your /public folder before building the production version of your site. It’s only acting like a placeholder here, for development purposes]

Now it’s time to meet Dragon no.2!

The default Isso styling is not bad; I only tweaked the input box sizes and positions a bit and changed the Submit button’s colour scheme to fit with my site. However, if you want to really go to town on the styling, you might have noticed one more slight problem: Because the comments box is a dummy, not really being served up by Isso, there is no dynamically generated content. In other words, no comments.

Like I said, the default Isso styling will probably be fine. So you don’t really need to bother with this next step. But if you really want a realistic preview of how Isso will look on your site—with comments—it’s time to play another trick on Hugo Server.

This time, you’re going to provide some dummy comments to populate the pages. As before, you’ll only want these to be displayed during development, not on your actual site. But, this time, it’s a lot less hassle, as it just involves injecting some dummy HTML into the template, rather than creating physical files and folders.

First of all, change the code you embedded in your /themes/yourthemename/layouts/_default/single.html template, from this:

{{"<!-- begin comments //-->" | safeHTML}}
<section id="isso-thread">

{{"<!-- end comments //-->" | safeHTML}}

to this:

{{"<!-- begin comments //-->" | safeHTML}}
<section id="isso-thread">
    {{"<!-- include dummy comments when on localhost //-->" | safeHTML}}
    {{ if eq (printf "%v" $.Site.BaseURL) "http://localhost:1313/" }}
    {{ partial "dummycomments.html" . }}
    {{"<!-- end include dummy comments when on localhost //-->" | safeHTML}}
{{"<!-- end comments //-->" | safeHTML}}

What this does is; it adds a simple conditional statement whereby, only if you’re developing locally [ie. the site is running on http://localhost:1313], Hugo will embed a dummycomments.html partial in the page. As you’ve probably guessed, the dummycomments.html partial [which you’re going to create next], contains a selection of dummy comments to populate the page.

Create the new partial in /themes/yourthemename/layouts/partials/dummycomments.html. As with the Javascript from earlier, the comments would usually be generated dynamically by Isso, so you’ll need an example of properly formatted dummy comment code, to put in the partial and recreate this. In the finest traditions of Blue Peter, here’s one I made earlier…​

Once you’ve added added the dummycontents.html partial to your Hugo site theme and Hugo has rebuilt the site, you should see that, as well as a dummy comments box, you now have some dummy comments, to whet your CSS chops on.


OK? Good. Just one last piece of business to take care of, before you crawl bloodied but unbowed over the finish line. Let’s attend to those systemd startup scripts I mentioned earlier.

Time to log back into your server.

[server] Create Server Startup Scripts

Once you’ve logged back into your server, it’s time to create some systemd startup scripts to run Caddy and Isso as system services. This means they will automatically run when your server boots and will also be automatically relaunched, if they crash. Such systemd startup scripts live in /etc/systemd/system/ and need to be edited [using your editor of choice] using sudo, as they belong to root.

[BTW: it doesn’t matter whether or not you’re still working in your Pipenv virtual environment for this bit, as the files you’re editing aren’t part of the Virtual Environment].

Let’s do the Caddy one first:

$: sudo nvim /etc/systemd/system/caddy.service

Here’s my caddy.service script:

Description=Caddy Server
#launch caddy after system networking stuff has loaded

#your name
#group caddy runs under when serving web pages
#where Caddyfile lives
#max file dscriptor [equivalent of ulimit in terminal]
#caddy unix socket location
#location of caddy binary + your email addy [needed as caddy automatically sets up
#letsencrypt SSL certs for your site and needs your email to agree the T&Cs
ExecStart=/usr/local/bin/caddy -agree -email you@yoursite.com -pidfile=/var/run/caddy/caddy.pid
#respawn caddy process if it crashes
#wait 10 mins to respawn, if it's crashing constantly

#target [??? not sure what this means]

#don't forget to enable it: sudo systemctl enable caddy

Once you’ve saved that script, you need to enable it:

$: sudo systemctl enable caddy.service

Created symlink from /etc/systemd/system/multi-user.target.wants/caddy.service to /etc/systemd/system/caddy.service.

[If you ever want to disable the service run sudo systemctl disable caddy.service]

[If you ever need to edit an existing service script, run sudo systemctl daemon-reload afterwards to reload it]

Now, test your script to make sure it works.

#kill caddy, if it's running
$: kill -SIGINT $(pgrep caddy)

[2]  + 12158 done       caddy

#and try launching it as a service
$: sudo service caddy start

#you won't get any output [unless there are errors]
#confirm Caddy is running with
$: sudo service caddy status

● caddy.service - Caddy Server
   Loaded: loaded (/etc/systemd/system/caddy.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2017-02-25 19:24:04 GMT; 1min 14s ago
 Main PID: 13216 (caddy)
   CGroup: /system.slice/caddy.service
           └─13216 /usr/local/bin/caddy -agree -email you@yoursite.com -pidfile=/var/run/caddy/caddy.pid

Feb 25 19:24:04 yourservername systemd[1]: Started Caddy Server.
Feb 25 19:24:05 yourservername caddy[13216]: Activating privacy features... done.
Feb 25 19:24:05 yourservername caddy[13216]: https://www.yoursite.com
Feb 25 19:24:05 yourservername caddy[13216]: https://yoursite.com
Feb 25 19:24:05 yourservername caddy[13216]: http://www.yoursite.com
Feb 25 19:24:05 yourservername caddy[13216]: http://yoursite.com

All good? Cool! Now the final step. Let’s create a similar startup script for Isso:

$: sudo nvim /etc/systemd/system/isso.service

Here’s my isso.service script:

Description=Isso Comments
#start after networking stuff has loaded

#your name
#group webserver runs under when serving web pages
#max file dscriptor [equivalent of ulimit in terminal]
#location of isso script to run & location of its config file
#note the isso script is in the `bin` folder in your virtual environment
#you don't need to activate your virtual environment here
#it will automatically be activated when the isso script within it runs
ExecStart=/home/yourname/isso/.venv/bin/isso  -c /home/yourname/isso/isso.cfg
#respawn isso process if it crashes
#wait 10 mins to respawn, if it's crashing constantly

#target [??? not sure what this means]

#don't forget to enable it: sudo systemctl enable isso

As before, once you’ve saved that script, you need to enable it:

$: sudo systemctl enable isso.service

Created symlink from /etc/systemd/system/multi-user.target.wants/isso.service to /etc/systemd/system/isso.service.

[If you ever want to disable the service run sudo systemctl disable isso.service]

[If you ever need to edit an existing service script, run sudo systemctl daemon-reload afterwards to reload it]

Now, test your script to make sure it works.

#kill isso, if it's running
$: kill -SIGINT $(pgrep isso)

[1]  + 11893 done       isso -c ~/isso/isso.cfg

#and try launching it as a service
$: sudo service isso start

#you won't get any output [unless there are errors]
#confirm isso is running with
$: sudo service isso status

● isso.service - Isso Comments
   Loaded: loaded (/etc/systemd/system/isso.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2017-02-25 20:00:01 GMT; 4s ago
 Main PID: 15577 (isso)
   CGroup: /system.slice/isso.service
           └─15577 /home/yourname/isso/.venv/bin/python3 /home/yourname/isso/.venv/bin/isso -c /home/yourname/isso/isso.cfg

Feb 25 20:00:01 yourservername systemd[1]: Started Isso Comments.

If you want to make really sure everything’s ticking over nicely, you can reboot your server just to check that both Caddy and Isso start automatically, as they should. But, at long-bleeding-last, you should now have Isso comments working on your Hugo powered website!


Phew!—I don’t know about you but, after that lot, I could do with a nice…​.

Image credit: erikskaar.com

By the way, I’m happy to try and answer any questions you have about this. But please bear in mind that I only arrived at this working solution, after much wailing, gnashing of teeth, rending of raiment and head-butting of fucking wall. So take any advice I offer with a pinch of scepticism. My veneer of competence is exceedingly thin!

Postscript: Overriding Isso’s Built-in CSS

One of the annoying things about Isso which has come up in the comments is that the CSS styling information for a lot of the comment interface is hard-wired into the Isso javascript itself, which makes it a bugger to style, as those hard-wired styles often take precedence over ones you’ve defined in your CSS

Now, you could start using !important directives in your CSS to try and wrest back control. But this is generally considered a 'bad thing', as it messes with the fundamental 'cascading' nature of CSS. So is best avoided.

A better [if slightly more labour intensive] way to 'Take Back Control' [where have I heard that before?] is just to nullify the hard-wired CSS inside Isso and use your own CSS for the whole thing. This has the advantage of allowing you to keep all your Isso related styling under your own control and the disadvantage of requiring you to write CSS to cater for all the elements comprising the Isso interface. 'Swings and Roundabouts', as they say.

It’s a simple matter of adding data-isso-css="false" to the Isso javascript embed link in your site’s header. So, for example, I changed mine from:

<script data-isso="{{ .Site.BaseURL }}isso/" src="{{ .Site.BaseURL }}isso/js/embed.min.js"></script>


<script data-isso="{{ .Site.BaseURL }}isso/" src="{{ .Site.BaseURL }}isso/js/embed.min.js" data-isso-css="false"></script>

And now my Isso comments are completely styled from within my own CSS. Hurrah!

server {

    //blah blah... other server config

    //proxy to Isso
    location /isso/ {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Script-Name /isso;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://localhost:8080;
        } //end Isso proxy block

} //end server block
Back to Top