๐Ÿš€ Setting Up My Hugo Stack Blog Site on Ubuntu with Docker

Recently, I decided to give my blogging workflow an upgrade by setting up a Hugo Stack blog site on my Ubuntu Linux environment. I wanted Sass support and a clean card-style theme, so after a bit of research, I landed on the Stack theme v3.30.0 for Hugo, and opted to use the official hugomods Docker image for convenience and consistency. Here’s how it all went down.


๐Ÿ“ฆ Step 1: Choosing the Right Docker Image

Since I needed Sass (along with PostCSS, Autoprefixer, and other tools), I selected the Docker image tag that ends with exts. This variant includes:

  • Embedded Dart Sass
  • PostCSS CLI
  • AsciiDoc
  • Pandoc
    …and a few other goodies.

I pulled the image with:

docker pull hugomods/hugo:exts-0.146.7

๐Ÿ—๏ธ Step 2: Creating a Hugo Site with the Stack Theme

After confirming that my Hugo version met the minimum (v0.128.0), I set up a new Hugo site like this:

hugo new site blog.my.fred
cd blog.my.fred
git init
git submodule add https://github.com/CaiJimmy/hugo-theme-stack.git themes/stack
echo "theme = 'stack'" >> hugo.toml
hugo server

Simple and straightforward. I loved how quickly I was able to spin this up.


๐Ÿงฑ Step 3: Leveraging the Starter Template

I used the hugo-theme-stack-starter repository to bring in some useful defaults and assets:

mkdir ./temp && cd ./temp
git clone https://github.com/CaiJimmy/hugo-theme-stack-starter
cp -rf ./hugo-theme-stack-starter/assets/* ./../assets/
cp -rf ./hugo-theme-stack-starter/config ./../config
cp -rf ./hugo-theme-stack-starter/content/* ./../content

This gave me a great starting point with a working config and example content.


โš™๏ธ Step 4: Tweaking the Configuration

I made sure to update config/_default/config.toml with my custom baseURL, and updated the theme path in config/_default/module.toml. Disable Disqus content by removing something like [Params.Disqus] ShortName in config/_default/config.toml.


๐ŸŒ Step 5: Replacing External Resources with Local Assets

The theme loads various JS and CSS from external CDNs. For better performance and offline compatibility, I replaced those with local versions:

  1. Created a static/vendor directory.
  2. Downloaded the necessary files via wget, except two files from Cactus Chat that required manual download from GitLab.
  3. Updated themes/stack/data/external.yaml to point to the local versions.
cd static/vendor && \
wget https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js && \
wget https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js && \
wget https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js && \
wget https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css && \
wget https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css && \
wget https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css && \
wget https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js && \
wget https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js && \
wget https://latest.cactus.chat/cactus.js && \
wget https://latest.cactus.chat/style.css

Then, updated the YAML from:

Vibrant:
    - src: https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js
      integrity: sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM=
      type: script

PhotoSwipe:
    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js
      integrity: sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo=
      type: script
      defer: true

    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js
      integrity: sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU=
      type: script
      defer: true

    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css
      type: style

    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css
      type: style

KaTeX:
    - src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css
      integrity: sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV
      type: style

    - src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js
      integrity: sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8
      type: script
      defer: true

    - src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js
      integrity: sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05
      type: script
      defer: true

Cactus:
    - src: https://latest.cactus.chat/cactus.js
      integrity:
      type: script
    - src: https://latest.cactus.chat/style.css
      integrity:
      type: style

like so:

Vibrant:
  - src: /vendor/vibrant.min.js
    type: script

PhotoSwipe:
  - src: /vendor/photoswipe.min.js
    type: script
    defer: true

  - src: /vendor/photoswipe-ui-default.min.js
    type: script
    defer: true

  - src: /vendor/default-skin.min.css
    type: style

  - src: /vendor/photoswipe.min.css
    type: style

KaTeX:
  - src: /vendor/katex.min.css
    type: style

  - src: /vendor/katex.min.js
    type: script
    defer: true

  - src: /vendor/auto-render.min.js
    type: script
    defer: true

Cactus:
  - src: /vendor/cactus.js
    type: script

  - src: /vendor/style.css
    type: style

๐Ÿงช Step 6: Running Hugo via Docker

Final step โ€” serving the site using Docker:

docker run -d \
    --name my-blog \
    -p 1313:1313 \
    -v /ServiceData/my_hugo:/blog.my.fred \
    hugomods/hugo:exts-0.146.7 \
    hugo server --source '/blog.my.fred' --bind '0.0.0.0' --port 1313 --baseURL '192.168.52.60'

Everything spun up perfectly. I was able to access my blog at http://192.168.52.60:1313.


๐Ÿ“ Final Thoughts

Setting up a Hugo blog with Docker was smoother than I expected. The Stack theme looks modern and polished, and Docker keeps everything isolated and reproducible. If you’re a Linux user looking to get into blogging or static site generation, I highly recommend giving Hugo + Docker a shot!


Would you like to include any screenshots or mention your next steps (like deploying to a server)?