This is the exact stack behind this blog. If you want a fast, self-deploying tech blog with zero servers, zero monthly bills, and VS Code as your entire CMS — here is how to build it.

By the end you will have:

  • A Hugo blog with the PaperMod theme
  • VS Code as your editor, file manager, git client, and terminal — all in one
  • Automatic deploys to Cloudflare Pages on every git push

Why this stack

Hugo is a static site generator. It turns markdown files into a complete website in milliseconds. No database, no runtime, nothing to keep running.

PaperMod is a clean Hugo theme with sensible defaults out of the box: reading time, code copy buttons, table of contents, dark mode, tags — all working without configuration.

Cloudflare Pages hosts the output globally with HTTPS, CDN caching, and branch preview URLs — free on the free tier.

GitLab CI is the automation layer. Every push triggers a pipeline that builds the site and deploys it to Cloudflare. You never manually run a deploy.

VS Code handles everything local: editing markdown, previewing files, running the Hugo dev server in the integrated terminal, and committing/pushing via the built-in Source Control panel. You never need a separate CMS.


Prerequisites

Windows users: all commands in this guide are Linux/macOS shell commands. The easiest path is WSL 2 (Windows Subsystem for Linux) — run wsl --install in an elevated PowerShell, then open VS Code and install the WSL extension. Everything below works identically inside WSL.


Step 1: Create the site

Open a terminal (VS Code integrated terminal works perfectly — Ctrl+`):

hugo new site my-blog
cd my-blog
git init
git checkout -b main

Open the project in VS Code:

code .

From this point on, VS Code is your CMS. The Explorer panel on the left is your file browser. The integrated terminal runs Hugo. The Source Control panel handles all git operations.


Step 2: Add the PaperMod theme

In the VS Code terminal:

git submodule add --depth 1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod

Then open .gitmodules in VS Code and add shallow = true:

[submodule "themes/PaperMod"]
    path = themes/PaperMod
    url = https://github.com/adityatelange/hugo-PaperMod.git
    shallow = true

This keeps CI clones fast by fetching only the latest theme commit.


Step 3: Configure hugo.toml

Open hugo.toml in VS Code (it’s in the root of your project) and replace its contents:

baseURL = "https://your-project.pages.dev/"
languageCode = "en-us"
title = "Your Blog Name"
theme = "PaperMod"
enableRobotsTXT = true
enableGitInfo = true

[pagination]
pagerSize = 10

[permalinks]
posts = "/:slug/"

[taxonomies]
tag = "tags"
category = "categories"

[params]
description = "Your blog description."
author = "Your Name"
ShowReadingTime = true
ShowCodeCopyButtons = true
ShowPostNavLinks = true
ShowBreadCrumbs = true
ShowToc = true
mainSections = ["posts"]

[params.homeInfoParams]
Title = "Your tagline here"
Content = "Short description of what you write about."

[[menu.main]]
identifier = "posts"
name = "Posts"
url = "/posts/"
weight = 10

[outputs]
home = ["HTML", "RSS", "JSON"]

Leave baseURL as a placeholder for now — you will update it with your real URL in Step 6.


Step 4: Write your first post

In the VS Code terminal:

hugo new content posts/my-first-post.md

VS Code will show the new file in the Explorer under content/posts/. Click it to open and edit. The front matter Hugo generates looks like:

---
title: "My First Post"
date: 2026-01-01T00:00:00Z
draft: true
---

Fill it out and set draft: false when the post is ready:

---
title: "My First Post"
date: 2026-01-01T00:00:00Z
draft: false
description: "A short summary of this post."
tags: ["example"]
categories: ["meta"]
---

Your post content here.

Step 5: Push to GitLab

Create a new blank project on GitLab (no README, no .gitignore). Then connect and push from the VS Code terminal:

git remote add origin [email protected]:your-username/my-blog.git
git add .
git commit -m "init: hugo blog with PaperMod"
git push -u origin main

After the first push, you can use VS Code’s Source Control panel (Ctrl+Shift+G) for all future commits and pushes — no terminal needed.


Step 6: Create the Cloudflare Pages project

Install Wrangler:

npm install -g wrangler@4
wrangler login
wrangler whoami

Create the Pages project (one-time only):

wrangler pages project create my-blog --production-branch main

Wrangler prints your site URL: https://my-blog-xxxx.pages.dev. Open hugo.toml in VS Code and update baseURL to that URL. Commit the change:

git add hugo.toml
git commit -m "chore: set baseURL to Cloudflare Pages URL"

Step 7: Create a Cloudflare API token

Your CI pipeline needs permission to deploy.

  1. Open Cloudflare → API Tokens
  2. Create TokenCustom token
  3. Permission: Account → Cloudflare Pages → Edit
  4. Copy the token — you will not see it again

Also copy your Account ID from the wrangler whoami output.


Step 8: Add the CI pipeline

In VS Code, create a new file .gitlab-ci.yml in the project root and paste:

stages:
  - deploy

variables:
  GIT_SUBMODULE_STRATEGY: recursive
  HUGO_ENV: production

deploy_cloudflare_pages:
  stage: deploy
  image: node:20
  before_script:
    - apt-get update -qq && apt-get install -y -qq curl git ca-certificates jq
    - HUGO_VERSION=$(curl -fsSL https://api.github.com/repos/gohugoio/hugo/releases/latest | jq -r '.tag_name' | sed 's/v//')
    - HUGO_TARBALL="hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
    - curl -fsSL -o /tmp/hugo.tar.gz "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_TARBALL}"
    - tar -xzf /tmp/hugo.tar.gz -C /tmp
    - mv /tmp/hugo /usr/local/bin/hugo && chmod +x /usr/local/bin/hugo
    - npm install -g wrangler@4
  script:
    - hugo --gc --minify
    - wrangler pages deploy public --project-name "${CLOUDFLARE_PAGES_PROJECT}" --branch "${CI_COMMIT_REF_NAME}"
  rules:
    - if: '$CLOUDFLARE_PAGES_PROJECT == null'
      when: never
    - if: '$CI_COMMIT_BRANCH'

This pipeline automatically fetches the latest Hugo Extended release from the GitHub API on every run — no version pinning to maintain.


Step 9: Add GitLab CI variables

In GitLab → your project → Settings → CI/CD → Variables, add:

VariableValueNotes
CLOUDFLARE_API_TOKENyour API tokenMark as Masked
CLOUDFLARE_ACCOUNT_IDyour account ID
CLOUDFLARE_PAGES_PROJECTmy-blogMust match the name from Step 6

Step 10: Deploy

Commit and push from VS Code Source Control panel or terminal:

git add .
git commit -m "ci: add GitLab CI pipeline"
git push

Go to GitLab → CI/CD → Pipelines. When the job goes green, your site is live at the Cloudflare Pages URL.


Step 11: Attach a custom domain (optional)

  1. Cloudflare Pages → your project → Custom domains → Set up a custom domain
  2. Enter your domain (e.g. blog.yourdomain.com)
  3. If DNS is managed in the same Cloudflare account: records are created automatically
  4. If external DNS: add CNAME blog → your-project.pages.dev
  5. Update baseURL in hugo.toml to your real domain, commit, and push

HTTPS is provisioned automatically.


The daily workflow

Once everything is set up, writing a post is:

  1. New post: hugo new content posts/post-title.md in the VS Code terminal
  2. Edit: Click the file in the Explorer panel, write in VS Code
  3. Publish: In Source Control panel — stage → commit → push

That’s it. GitLab CI handles the build and deploy. You never touch a server.