As a developer and security professional, I wanted my online presence to be more than just a resume, I wanted it to be a live demonstration of my skills. My goal was to host two distinct sites: a static portfolio at naol.dev and a technical blog at blog.naol.dev. The challenge? I wanted to manage both from a single GitHub repository, a monorepo with a fully automated deployment pipeline.
This isn’t supported by GitHub Pages, which only serves one site per repository. But by combining the strengths of GitHub Actions, Cloudflare Pages, and Cloudflare DNS, I built a seamless, hybrid CI/CD pipeline that does exactly what I need.
This article is the complete, step-by-step guide on how I did it, and how you can too.
The Architectural Overview #
Before we dive in, let’s look at the final architecture. This is a hybrid hosting model that leverages the best tool for each job.
- Source Code: A single GitHub repository with a
mainbranch. The root contains the portfolio’s static files, and a/blogsubdirectory contains the Hugo source code. - Portfolio (
naol.dev): Deployed via a GitHub Actions workflow to GitHub Pages. This workflow is smart and only runs when portfolio files are changed. - Blog (
blog.naol.dev): Deployed via Cloudflare Pages. This service connects directly to the GitHub repo and is configured to only build from the/blogsubdirectory. - DNS & Routing: Cloudflare DNS sits in front of everything, directing traffic for each domain to the correct hosting platform.
Figure: Hybrid CI/CD Pipeline Architecture
Now, let’s walk through how to set this up, step by step.
Step 1: Local Project Structure #
A clean local structure is key. In your main project folder, you should have two distinct parts:
portfolio-website/
├── .github/workflows/ # Will contain our deployment workflow
├── assets/ # Assets for the main portfolio
├── blog/ # The complete Hugo project lives here
│ ├── content/ # Blog posts are stored here
│ ├── themes/
│ └── assets/
├── .gitignore
├── index.html # Main portfolio page
└── style.css # Main portfolio stylesheet
Step 2: Setting Up Cloudflare DNS #
Before we configure any deployments, we need to tell the internet where to send traffic. We’ll use Cloudflare to manage our DNS.
- Sign up for a free Cloudflare account and add your domain, in my case (
naol.dev). - Change Nameservers: Follow Cloudflare’s instructions to update your nameservers at your domain registrar (e.g., Namecheap). This officially puts Cloudflare in control.
- Configure DNS Records: In the Cloudflare DNS dashboard, you need two sets of records:
- For the Portfolio (
naol.dev): FourArecords pointing your root domain (@ornaol.dev) to the GitHub Pages IP addresses. Ensure the “Proxy status” cloud is orange.A naol.dev 185.199.108.153 Proxied A naol.dev 185.199.109.153 Proxied A naol.dev 185.199.110.153 Proxied A naol.dev 185.199.111.153 Proxied - For the Blog (
blog.naol.dev): You just need a placeholderCNAMErecord. Cloudflare Pages will manage this for you later, but creating it now is good practice.CNAME blog your-username.github.io Proxied
- For the Portfolio (
Step 3: Deploying the Blog with Cloudflare Pages #
We’ll set up the blog first, as it’s a straightforward process.
- In your Cloudflare dashboard, navigate to Workers & Pages.
- Click Create application > Pages > Connect to Git.
- Select your
portfolio-websiterepository. - In the Build settings, configure it as follows:
- Project name:
blogor anything you want - Production branch:
main - Framework preset:
Hugo - Build command:
hugo - Build output directory:
public - Root Directory (advanced):
blog<– This is important.
- Project name:
- Click Save and Deploy. Cloudflare will now build and deploy your blog from the
/blogsubdirectory. - Once deployed, go to the project’s Custom domains tab and add
blog.naol.dev. Cloudflare will verify it automatically.
Step 4: Deploying the Portfolio with GitHub Actions #
Now, let’s create the workflow that handles your main site.
- In your local repository, create a file at
.github/workflows/static.yml. - Paste the following code into the file. This workflow is smart, it will not run if you only change files inside the
/blogfolder.
# .github/workflows/static.yml
name: Deploy Main Portfolio
on:
push:
branches: ["main"]
paths-ignore:
- 'blog/**' # Won't run when only blog files are changed
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "github-pages"
cancel-in-progress: true
jobs:
deploy:
name: Deploy Portfolio Website (naol.dev)
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload Artifact
uses: actions/upload-pages-artifact@v3
with:
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- Finally, configure your repository’s GitHub Pages settings:
- Go to Settings > Pages.
- Set the source to GitHub Actions.
- Set the custom domain to
naol.dev.
The Final Workflow in Action #
Now, your entire CI/CD pipeline is live.
- When you push changes to your portfolio files (like
index.html), thestatic.ymlworkflow triggers, updating your site atnaol.dev. - When you push changes to your blog files (anything inside the
blog/folder), the portfolio workflow is skipped, and Cloudflare Pages automatically detects the change, builds your Hugo site, and updates your blog atblog.naol.dev.
This hybrid approach gives you the best of both worlds: the simplicity of GitHub Pages for your static site and the powerful, monorepo-aware build capabilities of Cloudflare Pages for your blog, all managed from a single, clean repository.
If you want to see the final code, the workflow files, and the structure in action, you can check out the public repository for this project right here: