Cloudflare Pages and Hugo
Hello,#
I’ve finally decided to start this blog. The main reason? To have a place where I can share things I find interesting. Mostly tech-related topics, guides, and notes from my homelab setup.
A lot of what I write will probably be things I’ve figured out through trial and error. Hopefully it will be of use for someone. Either way I’ll get some new experiences.
Anyway, this first full post will go into the setup behind this site and provide a guide to building your own.
Components#
The components I use are Cloudflare Pages, Hugo, GitHub, and Umami for analytics. Why? Well, I already had my domains on Cloudflare, wanted to use Git more, and after googling a few different blog engines, I found Hugo. I’ve tested Ghost for another project, but this time I wanted to try something different, so Hugo it is.
To follow you’ll need to have a Cloudflare and Github account.
Install Git#
Git is a version control system that helps track changes in your project over time. It also lets you push your code to a remote hosting platform. For my setup, that’s GitHub but it could just as easily be another platform or even a self-hosted Git server.
To install and setup Git follow theese links:
Install Git on your preferred machine: Install Git Then configure it with SSH keys: Configure SSH keys.
Once done, move on to the next step to install and configure Hugo.
Install and run Hugo#
Hugo is a static site generator that takes content written in plain text (usually in Markdown) and builds it into a complete website. It’s fast, easy and free of charge.
First, download and install Hugo: Install Hugo
I used Chocolatey (a Windows package manager) to install the extended version with Sass support.
Once Hugo and Git are installed, create your first site using the command:
hugo new site my-new-site
This will create a Hugo instance with all the necessary files to run the site.
Hugo’s configuration lives in the hugo.toml file.
The most basic configuration might look like this:
baseURL = 'https://mynewsite.cloud/'
languageCode = 'en-us'
title = 'My New Site'
For more info visit: Hugo config
To build the site locally, run:
hugo server
Then visit the site at http://localhost:1313/
Use this to view the site before publishing it to Cloudflare.
Git#
This next step is to start a repo for tracking changes and creating a source of truth for your project. This is also where Cloudflare will build from.
Create an empty repo on Github then start tracking the files and adding them by running:
cd my-new-site # Jump into the new folder created by Hugo
git init # Initialize Git
git add . # Start tracking all files in the folder
git commit -m "first hugo site" # Create a commit with a message
git remote add origin git@github.com:<username>/<repo-name>.git # Point to your GitHub repo
git push -u origin main # Push the files to the remote repo
For every change you make, you’ll want to run:
git add . # Add all changes (lazy way)
git add /my-new-site/folderX/file.txt # Add specific files
git commit -m "I changed something" # Commit with a message
git push # Push to GitHub
For more information on how to work with Git, visit Github docs.
If you want to spice things upp with a theme created by someone else follow theese steps.
First find a theme on Hugo Themes.
Then visit their page and copy the Git URL.
Then add it as a submodule using
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo "theme = 'ananke'" >> hugo.toml
Currenlty I’m using Terminal but i’ve added it as local files in my repo because I wanted to make some minor changes and thought this would be the easiest way to accomplish this. If you want todo the same disregard the above step and clone the repo to the themes folder by running:
Git clone <repo>
Cloudflare#
Cloudflare is a global content delivery network (CDN) and internet security provider with a wide range of services. For my project, I’m using their Pages service to publish my blog and make it available on the web for others to view.
I chose this option because it’s free and fits my needs, but other services like GitHub Pages should also work if that’s a better fit for your setup.
It’s easy to set up and all it needs is a connection to your Git repo.
- Log in to your Cloudflare account.
- In the left menu, go to Workers & Pages, then switch to the Pages tab.
- Click “Create a project” or “Connect to Git” and select your repository (public or private—both work).
- In the build settings section:
- Choose Hugo as the framework preset.
- Leave the rest as default (Cloudflare will auto-fill based on the preset).
- Click Deploy.
Custom Domain (Optional)#
If you want to use your own domain instead of the default Cloudflare one:
- Go to your Pages project in the Cloudflare dashboard.
- Click on Custom Domains.
- Press “Set up a custom domain” and follow the steps.
This allows the site to be accessed via something like:
https://blog.peartech.se
Make sure your domain is managed by Cloudflare to take full advantage of their suite.
Analytics#
Cloudflare offers some great analytics and logging functions but I wantet to test a selfhosted option. Part of it is because I wanted to use my own docker instance and also because, why not? When starting out I didn’t know what to chose as there are plenty of options to chose from and I don’t have any experience with this kind of tech. Ultimatley I did the only sensible thing todo and started to google and found that many people used Umami as their choosen analytics engine. That’s also what I picked and added to my page.
One thing to note is that Umami’s API needs to be publicly reachable. This means that if you’re self-hosting, you will need to allow traffic into your server/container somehow. Depending on your setup and experience, this might not be something you’re comfortable with.
For my setup, I use a Cloudflare Tunnel to safely expose the Umami API without opening up unnecessary ports in my firewall. This provides an extra layer of security while ensuring the API remains accessible for external use.
First create an .env file with information abut your DB. The data could be inserted into the compose file but i choose to use an .env file as it’s a bit (not much) safer. It also makes it easier to port the compose file to another location or host it on github without exposing sensitive information.
APP_SECRET=my-super-duper-secret
DATABASE_URL=postgresql://sql_user:sql_password@db:5432/umami_db
DATABASE_TYPE=postgresql
HASH_SALT=<64chars>
POSTGRES_DB=umami_db
POSTGRES_USER=sql_user
POSTGRES_PASSWORD=sql_password
Inside the docker compose file you may have something like this
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
env_file: /dockerfolder/.env
container_name: umami
environment:
TRACKER_SCRIPT_NAME: umamidata
API_COLLECT_ENDPOINT: all
APP_SECRET: ${APP_SECRET}
labels:
- traefik.enable=true ##Traefik labels can be ignored if your not familliar with the service.
- "traefik.http.routers.umami.rule=Host(`umami.peartech.se`)"
- "traefik.http.routers.umami.tls.certresolver=letsencrypt"
- "traefik.http.routers.umami.entrypoints=websecure"
- "traefik.http.routers.umami.service=umami"
- "traefik.http.services.umami.loadbalancer.server.port=3000"
- "traefik.http.routers.umami.middlewares=authelia@docker"
# Without Traefik it can be exposed with this config
#ports:
#- "3000:3000"
depends_on:
- db
restart: always
db:
container_name: db
image: postgres:15-alpine
env_file: /dockerfolder/.env
volumes:
- db:/var/lib/postgresql/data
restart: always
volumes:
db:
Keep in mind that I expose my serivce via Traefik and I also secure it with Authelia. You can skip thoose steps if you like but will have to add the line
ports:
- "3000:3000"
to the Umami service if you’ll want to interact with Umamis management site from your local machine. Another way is to expose it via the Cloudflare tunnel but then you’ll have to figure out howto safeguard it from remote attackers. Should be possible with Cloudflares Zero Trust service but that will not be covered in this post.
Once done fire it up and logon to the service via the local IP and port 3000 or with the fqdn that you’ve configured traefik with. The default username is admin wiht the password umami. Change the password emidiatley and tehn go to settings and add your website. Once configured copy the tracking code as that will be use inside your site.
Insert the tracking code inside your site. For me, I had to add the line inside the baseof.html file that resides inside the _default folder.
<head>
<script defer src="https://umami.peartech.se/umamidata" data-website-id="123-123-123-123-123"></script>
...
...
</head>
Rebuild the site and push the newly edited file to the repo.
Cloudflare Tunnel#
To expose services in a safe and secure manner, Cloudflare Tunnels can be used. They act as a secure bridge between your host and the internet, allowing external access without exposing your server directly.
With Cloudflare Tunnels, you have full control over who can access your services—it won’t let anyone in without your explicit approval. This is a great way to make services publicly available without opening up your entire server to the internet.
Setup#
Before using Docker Compose to manage the tunnel, you’ll need to create it and obtain the necessary token from Cloudflare.
- Navigate to the Zero Trust section (previously known as Cloudflare Access).
- Create a new tunnel and copy the token. Important: the full token won’t be fully visible on the site after creation, so make sure to copy and save it immediately.
Save the token into your .env file like so:
CF_TOKEN=<your-tunnel-token>
Update Compose File#
Add the Cloudflare Tunnel container to your docker-compose.yml:
cloudflaretunnel:
container_name: cftunnel
dns: 1.1.1.1
image: cloudflare/cloudflared:latest
restart: unless-stopped
environment:
- TUNNEL_TOKEN=${CF_TOKEN}
command: tunnel --no-autoupdate run
Start the container:
docker compose up -d
Then refresh your Cloudflare dashboard. You should now see the tunnel listed with the status Healthy.
Add Public Hostnames#
Next, configure two public hostnames for your tunnel. These will route traffic to Umami:
- One hostname should match your Umami FQDN and point to the path you’ve configured as
TRACKER_SCRIPT_NAME(e.g.,umamidata). - The second should point to
/api.
Depending on your setup, you can either:
- Use Traefik (if you’re running it as a reverse proxy),
- Or point directly to the Umami container (e.g.,
http://umami:3000).
Hit save and test the setup by visiting the site.
Then log in to Umami’s dashboard.
If everything is configured correctly, your visit should show up on the dashboard along with some basic data about you. Should see data about your browser, device, and referrer etc.
Thats all for now. Have a great day!