๐ 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:
- Created a
static/vendor
directory. - Downloaded the necessary files via
wget
, except two files from Cactus Chat that required manual download from GitLab. - 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)?