Copied to Clipboard
, Tahoma, Geneva, Verdana, sans-serif;
background: #0b0b0f;
color: #e8e8f0;
line-height: 1.6;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 5%;
background: rgba(11, 11, 15, 0.95);
position: sticky;
top: 0;
z-index: 100;
border-bottom: 1px solid #1e1e2e;
}
.nav-brand {
font-size: 1.25rem;
font-weight: 700;
color: #f6821f; /* Cloudflare orange */
}
.nav-links {
list-style: none;
display: flex;
gap: 2rem;
}
.nav-links a {
color: #e8e8f0;
text-decoration: none;
font-size: 0.9rem;
transition: color 0.2s;
}
.nav-links a:hover { color: #f6821f; }
#hero {
padding: 8rem 5% 6rem;
max-width: 800px;
}
#hero h1 {
font-size: 3.5rem;
font-weight: 800;
margin-bottom: 0.5rem;
}
#hero h2 {
font-size: 1.5rem;
color: #00d4ff;
font-weight: 400;
margin-bottom: 1.5rem;
}
#hero p {
font-size: 1.1rem;
color: #9090a8;
margin-bottom: 2rem;
max-width: 600px;
}
.hero-cta { display: flex; gap: 1rem; flex-wrap: wrap; }
.btn-primary {
background: #f6821f;
color: #0b0b0f;
padding: 0.75rem 2rem;
border-radius: 4px;
text-decoration: none;
font-weight: 700;
transition: opacity 0.2s;
}
.btn-primary:hover { opacity: 0.85; }
.btn-secondary {
border: 1px solid #f6821f;
color: #f6821f;
padding: 0.75rem 2rem;
border-radius: 4px;
text-decoration: none;
font-weight: 600;
}
section {
padding: 5rem 5%;
max-width: 1200px;
margin: 0 auto;
}
section h2 {
font-size: 2rem;
font-weight: 800;
margin-bottom: 2rem;
border-left: 4px solid #f6821f;
padding-left: 1rem;
}
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
.project-card {
background: #1a1a28;
border: 1px solid #1e1e2e;
border-radius: 8px;
padding: 1.5rem;
transition: border-color 0.2s;
}
.project-card:hover { border-color: #f6821f; }
.project-card h3 { font-size: 1.1rem; margin-bottom: 0.75rem; }
.project-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 1rem 0;
}
.project-tags span {
background: rgba(246, 130, 31, 0.1);
border: 1px solid rgba(246, 130, 31, 0.3);
color: #f6821f;
padding: 0.2rem 0.6rem;
border-radius: 3px;
font-size: 0.8rem;
}
.project-links { display: flex; gap: 1rem; margin-top: 1rem; }
.project-links a { color: #f6821f; text-decoration: none; font-size: 0.9rem; }
Step 1.4 — Test Locally
# Python local server (usually pre-installed)
python3 -m http.server 8000
# Visit: http://localhost:8000
Make sure everything renders correctly before pushing.
Part 2: Push to GitHub
Step 2.1 — Initialise Git
cd my-portfolio
git init
git add .
git commit -m "feat: initial portfolio site"
Step 2.2 — Create the GitHub Repository
- Go to github.com/new
- Repository name:
my-portfolio
- Visibility: Public
- Do NOT initialise with a README — you already have files
- Click Create repository
Step 2.3 — Push
git remote add origin https://github.com/YOUR_USERNAME/my-portfolio.git
git branch -M main
git push -u origin main
Confirm:
git log --oneline
# Should show: feat: initial portfolio site
Part 3: Connect GitHub to Cloudflare Pages
This is where the CI/CD magic happens. Cloudflare Pages connects directly to your GitHub repository and automatically deploys on every push to main. No pipeline YAML to write — Cloudflare handles the deployment infrastructure for you.
Step 3.1 — Create a New Cloudflare Pages Project
- Log in to cloudflare.com
- In the left sidebar, go to Workers & Pages
- Click Create → Pages tab → Connect to Git
Step 3.2 — Authorise GitHub
- Click Connect GitHub
- Cloudflare opens a GitHub OAuth window
- Choose Only select repositories and select your
my-portfolio repo
- Click Install & Authorize
💡 You are granting Cloudflare read access to your repository so it can pull your code on each push. This is the same pattern used in every modern CI/CD integration — Cloudflare, Vercel, and Netlify all work this way.
Step 3.3 — Configure the Build Settings
After authorising, you will see the build configuration screen. For a pure static HTML/CSS/JS site:
| Setting |
Value |
| Production branch |
main |
| Framework preset |
None |
| Build command |
(leave empty) |
| Build output directory |
(leave empty) |
| Root directory |
(leave empty) |
Why no build command? Your site is plain HTML/CSS/JS — there is nothing to compile. Cloudflare Pages deploys the files exactly as they are in your repo. If you later add a framework (React, Next.js, Hugo), you would set the build command and output directory at that point.
Click Save and Deploy.
Step 3.4 — Watch the First Deployment
Cloudflare Pages will:
- Pull your code from GitHub
- Run the build (instant for static files)
- Deploy to the global edge network
- Give you a URL like:
https://my-portfolio-abc.pages.dev
The first deployment takes 30–60 seconds. Visit the .pages.dev URL to confirm your site is live.
From this point on, every git push to main automatically triggers a new deployment. You now have a fully managed CI/CD pipeline — no servers, no agents, no maintenance.
Part 4: Add Your Domain to Cloudflare
Step 4.1 — Add Your Domain to Cloudflare DNS
If your domain is not already managed by Cloudflare:
- In the Cloudflare dashboard, click Add a Site
- Enter your domain:
yourdomain.com
- Select the Free plan → Continue
- Cloudflare scans existing DNS records — review and keep any you need
- Click Continue to activation
Cloudflare will give you two nameservers, for example:
aria.ns.cloudflare.com
jay.ns.cloudflare.com
Step 4.2 — Update Nameservers at Your Registrar
Go to wherever you bought the domain:
- Find Nameservers or DNS Settings
- Replace the existing nameservers with Cloudflare's two nameservers
- Save
⚠️ Nameserver propagation takes 15 minutes to 48 hours — usually under an hour. Cloudflare sends an email when your domain is activated.
Step 4.3 — Verify Propagation
# Check your domain's nameservers
dig NS yourdomain.com +short
# Expected output (Cloudflare nameservers):
# aria.ns.cloudflare.com.
# jay.ns.cloudflare.com.
Or use whatsmydns.net — search for your domain with NS record type.
Part 5: Connect Your Custom Domain to Cloudflare Pages
Step 5.1 — Add the Custom Domain
- Go to Workers & Pages → your Pages project
- Click the Custom domains tab
- Click Set up a custom domain
- Enter:
yourdomain.com
- Click Continue
Cloudflare will check if your domain is managed in your account. Since you added it in Part 4, it will be found automatically.
Step 5.2 — Cloudflare Creates the DNS Record Automatically
This is one of the best parts of using Cloudflare Pages with a Cloudflare-managed domain: Cloudflare automatically creates the CNAME record pointing your domain to your Pages project. You do not touch DNS manually.
Cloudflare creates:
CNAME yourdomain.com → my-portfolio-abc.pages.dev (Proxied ✅)
Click Activate domain to confirm.
Step 5.3 — Add www Redirect (Recommended)
To make www.yourdomain.com also work:
- Go to Custom domains → Set up a custom domain again
- Enter:
www.yourdomain.com
- Cloudflare adds a second CNAME automatically
To redirect www to apex permanently:
- Go to Rules → Redirect Rules
- Create a rule: if
Hostname equals www.yourdomain.com → redirect to https://yourdomain.com (301 permanent)
Part 6: Configure SSL and Security
Cloudflare Pages handles SSL automatically — your site gets HTTPS the moment the custom domain is activated. No certificate purchasing, no Let's Encrypt commands, no renewal reminders. Cloudflare renews it automatically, forever.
Tighten these settings to production standard:
Step 6.1 — SSL/TLS Mode
- Go to your domain in Cloudflare → SSL/TLS → Overview
- Set encryption mode to Full (strict)
Cloudflare Pages serves over HTTPS natively, so Full (strict) works without any issue and is the most secure mode available.
Step 6.2 — Force HTTPS
-
SSL/TLS → Edge Certificates
-
Always Use HTTPS → ON
-
Automatic HTTPS Rewrites → ON
Any visitor hitting http://yourdomain.com is now redirected to https:// at Cloudflare's edge — before a request even touches your site.
Step 6.3 — HSTS (Advanced — Optional)
For maximum security, enable HTTP Strict Transport Security:
-
SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS)
- Enable HSTS → set Max Age to 6 months
- Check Include subdomains if you have subdomains
⚠️ HSTS tells browsers to never connect over HTTP for the specified period. Only enable this when HTTPS is fully confirmed and working.
Part 7: Performance Settings
These take under five minutes and have real-world impact:
Speed → Optimization:
| Setting |
Value |
| Auto Minify — HTML |
ON |
| Auto Minify — CSS |
ON |
| Auto Minify — JavaScript |
ON |
| Brotli compression |
ON |
| Early Hints |
ON |
Caching → Configuration:
| Setting |
Value |
| Caching Level |
Standard |
| Browser Cache TTL |
4 hours |
Security → Settings:
| Setting |
Value |
| Security Level |
Medium |
| Bot Fight Mode |
ON |
| Email Address Obfuscation |
ON |
💡 Bot Fight Mode silently challenges known bad bots at the edge. It is free, requires zero maintenance, and you should enable it on every site you run.
Part 8: Verify the Full Setup
# 1. Site loads over HTTPS with Cloudflare headers
curl -I https://yourdomain.com
# Expected:
# HTTP/2 200
# server: cloudflare
# content-type: text/html; charset=utf-8
# cf-ray: <id>-<datacenter>
# 2. HTTP redirects to HTTPS
curl -I http://yourdomain.com
# Expected:
# HTTP/1.1 301 Moved Permanently
# location: https://yourdomain.com/
# 3. www redirects to apex
curl -I https://www.yourdomain.com
# Expected: 301 to https://yourdomain.com
# 4. SSL certificate is valid
openssl s_client -connect yourdomain.com:443 -brief 2>/dev/null | grep -E "Verification|subject"
# 5. Check which Cloudflare PoP is serving you
curl -s -o /dev/null -w "Status: %{http_code} — CF-Ray: %header{cf-ray}\n" https://yourdomain.com
In the Cloudflare dashboard you now have:
-
Analytics → real-time requests, bandwidth, unique visitors, cache hit ratio
-
Security → threats blocked, bot traffic, firewall events
-
Pages → Deployments → full deployment history, build logs, preview URLs for every branch
Part 9: Your Ongoing Deployment Workflow
Every future update follows this pattern:
# 1. Make your change locally
vim index.html # add a new project, update bio, etc.
# 2. Commit with a meaningful message
git add .
git commit -m "feat: add HashiCorp Vault HA project card"
# 3. Push to GitHub
git push
# 4. Cloudflare Pages detects the push and deploys automatically
# Monitor at: dash.cloudflare.com → Workers & Pages → your project → Deployments
Deployment completes in under 30 seconds. The new version is live globally across 300+ Cloudflare edge locations simultaneously.
This is exactly how production static sites are deployed at scale. You just built the same pipeline engineering teams use at startups and enterprises. Write it on your CV.
Part 10: What Content to Put On It
Now that infrastructure is running, populate it with real content in this priority order:
Projects Section
For each project:
- One-sentence description of the problem it solves
- Technologies used — be specific: "Cloudflare Pages" not just "CDN"
- Link to the GitHub repository
- Link to your dev.to write-up if you published one
Troubleshooting
Site shows a Cloudflare error instead of your portfolio
- Confirm the custom domain is activated: Workers & Pages → Custom domains → green checkmark
- Check DNS has propagated:
dig yourdomain.com CNAME
- Wait 5–10 minutes and hard refresh
Build fails in Cloudflare Pages
- Go to Deployments tab → click the failed deployment → View build log
- For a static site with no build command, the most common cause is a misconfigured output directory
- Ensure Build output directory is empty (not
dist or build — those are for frameworks)
Custom domain not activating
- Confirm nameservers are on Cloudflare:
dig NS yourdomain.com
- Check your domain shows Active status under Websites in Cloudflare
- The domain must be active in Cloudflare before Pages custom domain recognition works
Mixed content warnings
- All
src= and href= attributes must use https:// or protocol-relative //
- Cloudflare's Automatic HTTPS Rewrites handles most of these automatically
Changes not appearing after push
- Check the Deployments tab — did the build succeed?
- Hard refresh:
Ctrl + Shift + R (Windows/Linux) / Cmd + Shift + R (Mac)
- Go to Caching → Purge Everything in Cloudflare if the old version persists
Architecture Summary
You (developer)
│
│ git push origin main
▼
GitHub Repository (source of truth)
│
│ Cloudflare Pages webhook detects push
▼
Cloudflare Pages Build Pipeline
├── Pulls latest code from GitHub
├── Runs build (instant for static HTML/CSS/JS)
└── Deploys to Cloudflare edge network
│
▼
Cloudflare Edge (300+ PoPs globally)
├── Serves yourdomain.com
├── SSL/TLS — Full (strict)
├── CDN caching
├── Always-HTTPS redirect
├── DDoS protection (always-on)
├── Bot Fight Mode
└── Brotli compression
│
▼
End User — fast, secure, globally distributed ✅
What This Proves on Your CV
Personal Portfolio Website — Deployed static site via Cloudflare Pages with GitHub CI/CD integration. Configured custom domain, Full (strict) SSL, HSTS, DDoS protection, bot filtering, Brotli compression, and edge caching. Site globally distributed across 300+ Cloudflare PoPs with automated deployments triggered on every commit to main.
That is seven production skills in a project that costs 10ドル/year to run.
Next Steps
-
Add Cloudflare Web Analytics — Analytics → Web Analytics → add your site. Free, privacy-respecting, no cookie banner needed
-
Add a contact form — Formspree or Web3Forms work without any backend
-
Write your first blog post and link it from the writing section — closes the loop on building in public
-
Add a Projects page — every Talent Forge project you complete gets a card here
-
Project 2: Containerise a real application with Docker and deploy via a full CI/CD pipeline
Resources
About the Talent Forge Program
This article is the official solution guide for Project 1 of the Talent Forge mentorship program — a free, structured DevOps curriculum run through NextGen Playground.
The program takes you from zero to a portfolio that gets you hired — through real projects, real infrastructure, and real documentation you can point employers at.
Join the program: discord.com/invite/fWUEh5x9PD
Enrol: forms.gle/XuD5a6nxi67xf7fY7
The GitLab variant (GitLab Pages + custom domain) is the companion article.