Deployment workflow

Why do you need to change the baseurl for production? When running locally, hugo will automatically swap out your baseurl’s domain for localhost:1313, so you shouldn’t need a different baseurl at all.


I’ve been using Grunt for things and love it. So I can run “grunt dev” and “grunt deploy” and so on. Of course this requires Node.js (but only on your machine, not your server). You also easily gain the ability to run various commands on things, like minifying CSS, JS, etc. The other cool thing is I pop open a web browser when I type “grunt dev” so it starts Hugo and opens up to my site (see the “open:devserver” command).

For example (Gruntfile.js):

module.exports = function(grunt) {


    shell: {
      options: {
        stdout: true
      server: {
        command: 'hugo server --theme=redlounge --buildDrafts --watch'
      build: {
        command: 'hugo -d build/ --theme=redlounge'
      deploy: {
        command: 'rsync -az --force --progress -e "ssh" build/'

    open: {
       devserver: {
         path: 'http://localhost:1313'

  grunt.registerTask('dev', ['open:devserver', 'shell:server']);

  grunt.registerTask('build', ['shell:build']);

  grunt.registerTask('deploy', ['shell:build', 'shell:deploy']);

Here’s my package.json so if anyone wants, they could basically copy this and run npm install. Then have The same grunt commands to use. Ensure this goes alongside your Hugo site. Or in other words, “public” and “themes” and “layouts” should all be sitting next to your Gruntfile.js and package.json.

  "name": "my-site",
  "version": "0.0.1",
  "homepage": "",
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-concat": "^0.4.0",
    "grunt-contrib-copy": "^0.5.0",
    "grunt-contrib-less": "^0.11.3",
    "grunt-contrib-uglify": "^0.5.0",
    "grunt-css": "^0.5.4",
    "grunt-open": "^0.2.3",
    "grunt-shell": "^0.7.0",
    "grunt-shell-spawn": "^0.3.0",
    "matchdep": "^0.3.0"

For people who use gh-pages on GitHub, I highly sugges using the approach/script below instead of the subtree method. This creates much less comit-noise (avoide the double commit …):


@spf13 first post here - first, thank you for Hugo. It seems like a great environment, with a vibrant community.

That rsync deploy makes sense to me, but, are you keeping any part of your site’s source tree in a git repo as well? How are you getting around the theme submodule issue mentioned in this thread.

Thank you in advance,

I use travis-ci with github to deploy. It works well for me.

Here is my write up on it -> (i know its a plug)

I’ve tried the tutorial many times, and always the CSS files don’t seem to load. I’m using the subtree method here:

Demo site should be here:

Can anyone tell me what I’m doing wrong? Seems canonical links are in the demo code, but not my headers. I’ve followed these instructions exactly, so many times. It must be something simple I’m missing, thanks.

Hi @cloudunicorn,

I took a quick look at your site, and here are my observations:

  1. Your site is at rather than

  2. In the rendered HTML, the stylesheets are listed as folllows:

     <link rel="stylesheet" href="/css/poole.css">
     <link rel="stylesheet" href="/css/syntax.css">
     <link rel="stylesheet" href="/css/lanyon.css">

    so the browser tries to look for etc., but of course found nothing. Try accessing instead. :slight_smile:

  3. You are using the Lanyon theme that you fetched from, right? If so, please take a look inside themes/lanyon/layouts/partials/head.html and see how the links to the stylesheets are defined?

Yes, you got it. The current Hugo port of Lanyon theme assumes that your website is placed in the root directory of your domain. Since you have placed your blog under the /hblog subdirectory, that theme does not work out of the box.

How to fix it? Try using the hyde or herring-cove themes instead. These were ported by Hugo’s author @spf13 himself, so this subdirectory issue has been taken care of. :wink:

Both hyde and herring-cove work great, right? Good! Now, as an exercise for you, please examine the partials in hyde and herring-cove, and figure out what you need to do to get the Lanyon theme to do the same, i.e. to be able to load your CSS properly.

So, no, I don’t think the CSS-fails-to-load issue has anything to do with . Your blog has been deployed correctly. It is just that you’ll need to tweak your “partials” to generate the desired HTML output to load the CSS files properly.

Hope this helps!


Thanks so much for the detailed response.

Is the demo not at a subdirectory? The URL is baseurl: “” as seen here:

and live site is at

And demo specifically says to use Lanyon. Only pointing out in case I’m not alone in following the demo instructions and running into this?

I fixed my issue by adding {{ .Site.BaseUrl }} to the css links so they read:

<link rel="stylesheet" href="{{ .Site.BaseUrl }}/css/poole.css">
<link rel="stylesheet" href="{{ .Site.BaseUrl }}/css/syntax.css">
<link rel="stylesheet" href="{{ .Site.BaseUrl }}/css/lanyon.css">

I don’t think the demo makes it obvious in any way this needs to be done, and it very much appears that the site will be served to a subdirectory of your github account.

I’ve seen lots of discussion about how to make Hugo approachable to ‘normals’ and this is where I’d start! If I can I’ll try to fork and edit myself.

Hi @cloudunicorn

Thank you for discovering these issues with our tutorials! You are absolutely right that we need to make Hugo approachable to newcomers, and fixing such problems our tutorials should be a top priority for our community indeed.

I am sorry that I spoke before I read—I did not read through the whole tutorial myself, and did not know it specifically mentions the use of the Lanyon theme.

Looking deeper, it seems @spencerlyon2, who contributed the original tutorial very early in March 2014, had ported the Lanyon theme himself, rather than using tummychow’s referenced by hugoThemes. My assumptions in my last post were incorrect.

Anyhow, moving forward, we have a few action items to work on:

  1. Fix issues in especially caveats when hosting in a subdirectory and how the {{ .Site.BaseUrl }} needs to be added to layouts/partials/*.html or layouts/chrome/head_includes.html as necessary. Also, if possible, update to Hugo v0.12 conventions.

  2. Submit an Issue or Pull Request to @spencerlyon2 to correct issues in, adding {{ .Site.BaseUrl }} to layouts/chrome/head_includes.html, and preferably converting layouts/chrome to layouts/partials.

  3. Submit an Issue or Pull Request to to add {{ .Site.BaseUrl }} to his layouts/partials/head.html too.

  4. [New info] Hugo v0.10 has canonifyurls = true as default, but for yet unknown reasons, Hugo v0.11 and beyond now has canonifyurls = false as the default, opposite to what is currently documented on This is the root cause of @cloudunicorn’s ordeal. Need to fix both code and doc. (Filed as Issue #802: The default of canonifyurls = true got reversed silently since v0.11)

Any volunteers? :slight_smile:

Thank you @cloudunicorn for your comments and contributions! Your suggestions help our Hugo community to grow and become better!


1 Like

Looking yet deeper into this, it seems that the real reason @cloudunicorn ran into problem was not because @spencerlyon2 forgot to put in {{ .Site.BaseUrl }}, but rather, it was not necessary to add {{ .Site.BaseUrl }} with Hugo v0.9 or Hugo v0.10, the latest stable version when @spencerlyon2 wrote his tutorial, because Hugo v0.10 by default “canonify”, i.e. automatically add the baseurl to all relative URLs!

  • Hugo v0.9 (2013-11-15): “Canonify” or “canonicalize” all relative URLs. Not configurable.

  • Hugo v0.10 (2014-03-01): Added canonifyurls option to keep URLs relative. The default is canonifyurls = true as documented, i.e. same as Hugo v0.9.

    commit 438c2198923022a3e4d299b06a7df18268041dd8
    Author: Phil Pennock
    Date: Fri Jan 3 18:36:53 2014 -0500

    Add canonifyurls config option.

    Be able to inhibit AbsURL canonicalization of content, on a site configuration basis. Advantages of being able to inhibit this include making it easier to rendering on other hostnames, and being able to include resources on http or https depending on how this page was retrieved, avoiding mixed-mode client complaints without adding latency for plain http.

  • Hugo v0.11 (2014-05-29): Somehow, the default seems to have become canonifyurls = false without anyone noticing, and without documentation change. Bug?

  • Hugo v0.12 and v0.13-DEV as of today: Ditto.

I wonder what happened… Hmm… More investigation needed, to find out which commit between v0.11 and v0.12 is responsible for the undocumented behaviour change, i.e. bug.

So, yet another possible answer to your question, @cloudunicorn, is simpler: Rather than adding {{ .Site.BaseUrl }} manually, you may alternatively add canonifyurls: true to your config.yaml, which will result in a correct and identical HTML with the correct URLs to your stylesheets.

1 Like

Thanks so much all. And now I don’t feel so crazy! Cheers, looking forward to using Hugo a lot more:)

Hi @cloudunicorn,

After some procrastination on my part (mea culpa), I have finally updated the docs:

Please check and and see if the revision looks okay to you. Comments welcome! :slight_smile:

1 Like

If you wanted to simplify deployments, you could connect your Github repo to CodeShip, then you have your tests and builds auto-deploy to your production server on pushes/pull requests to your repository.

Just wanted to mention that this function in .zshrc works perfectly for me, @spf13.

I had OS X Yosemite’s default rsync, which was v2.2 or something, so I used brew to tap dupes and upgrade it to 3.1.1. But it still does not have the --force or --progress switches.

In my case, my function looks like:

function hugorccdeploy {
  rm -rf /tmp/rcc
  hugo -s /Users/rc/dev/rcc/ -d /tmp/rcc
  rsync -avze "ssh -p 22" /tmp/rcc/

Just wanted to corroborate that this is working like a charm for me. When I run hugo server to test locally, my baseurl parameter gets overwritten automatically to localhost:1313 as @natefinch mentions, so I don’t have to make any changes to config.toml. I had a little glitchiness with trailing slash or no trailing slash in some css or javascript calls in the head, but that was easy to catch, just looking at the source.

Talking of deployment workflows, I automate it through hosted CI platforms but the gist of the idea could be as follows (if you were using github pages to host your website):

      # Set your environment variables
      export ACCNT_NAME=''
      export GITHUB_PAGES=''  # example would be

      # clone the github repo
      git clone$ACCNT_NAME/$GITHUB_PAGES.git

      # build the html files into the cloned repo (overwriting stuff)
      hugo -d $GITHUB_PAGES/

      # Automatically add all new files to be committed
      echo -e "a\n*\nq\n"|git add -i
      # commit all files with some log message and push
      git commit -vam 'Build done at - '$NOW_HOUR
      git push -v

You could wrap this all up in a bash/zsh function

I felt similarly to @sheki - I didn’t want to check my generated site in to git. So, I built something similar that leverages AWS for building and hosting my site. It deploys immediately after a git push and costs pennies to host per month. If you’re interested, check it out here:

This is how I do it.

1 Like

I have two questions:

  1. In the tutorial you have said:

On the server, there is another copy of Hugo; when Git on the server receives the pushed changes, it tells its neighbor Hugo to generate a fresh copy of the site.

Why do you need to have a copy of Hugo? when you do not need Hugo to be installed on the server at all?

  1. The second question is, in the post-receive file you have:


Is this where the local hugo site is, or where the hugo site is going on the VPS? Also in the Jekyll example it simply has

jekyll build

which doesnt appear to be a path, Im just wandering why this is.

Why do you need to install Hugo on the VPS, seeing as the beauty of a static site generator is that it generates the static and then you upload the static site to anywhere?

1 Like