[{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/cloudflare/","section":"Tags","summary":"","title":"Cloudflare","type":"tags"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/deep-dive/","section":"Tags","summary":"","title":"Deep dive","type":"tags"},{"content":"A Hugo site in a private GitHub repository, with its theme as a git submodule, deploys to Cloudflare Pages without going public: give Cloudflare\u0026rsquo;s GitHub App access to that one repository, pin HUGO_VERSION, and add the domain. On this site\u0026rsquo;s first deploy, on 2026-10-11, that took about 50 seconds to vinhdata.pages.dev and 40 more for vinhdata.com.\nI ran it through the Cloudflare API. Where the dashboard behaves differently, the text says so and the source is the Cloudflare documentation, not my run.\nGitHub, then Cloudflare. Numbers from the first deploy. The four hand-offs: the push reaches the GitHub App, the app hands the code to the Pages build, the build publishes to pages.dev, and the custom domain points there.\nWhat you need # Piece Here Hugo 0.166.0: Blowfish v3.8.0 declares Hugo 0.163.0 to 0.166.0. I use the extended edition; since Hugo 0.153.2 the standard one satisfies the theme too Theme Blowfish v3.8.0 as a submodule at themes/blowfish, public upstream Repository private, on GitHub Domain a zone already on Cloudflare DNS Output public/ 1. Give Cloudflare access to the private repository # Cloudflare reads your code through its GitHub App, Cloudflare Workers and Pages. A private repository is fine; what matters is that the app may see it.\nFirst time: the Pages setup in the dashboard installs the app and asks which repositories it may read. Already installed for another project: GitHub → your profile Settings → Applications → Installed GitHub Apps → Configure → Repository access, and add the repository. Choose \u0026ldquo;Only select repositories\u0026rdquo;: Cloudflare recommends it for organisations, and it fits a personal account too. When I created the project through the API before adding the repository, the call failed with \u0026ldquo;The project is linked to a repository that no longer exists\u0026rdquo;. The repository existed; the app could not see it. Cloudflare\u0026rsquo;s troubleshooting page lists a different message for this case, \u0026ldquo;The repository cannot be accessed\u0026rdquo;, so expect either.\n2. Set the build # Setting Value Why Production branch main Build command hugo --minify -b \u0026quot;${HUGO_BASEURL:-$CF_PAGES_URL}\u0026quot; production uses the real domain, previews their own *.pages.dev URL Output directory public Hugo\u0026rsquo;s default HUGO_VERSION 0.166.0, production and preview without it the build image installs its own default, older than Blowfish supports HUGO_BASEURL https://vinhdata.com/, production only canonical links and the sitemap use the domain Two things I did not have to configure:\nThe submodule. The build cloned the public theme submodule by itself. Cloudflare does not document private submodules; keep the theme public or vendor it into the repository. The edition. Setting HUGO_VERSION was enough; the build image installed the extended release on its own. The build log shows both:\nSubmodule path \u0026#39;themes/blowfish\u0026#39;: checked out \u0026#39;51a361e068b9975c7f1df8e2bb223db03fe49c2b\u0026#39; Detected the following tools from environment: hugo@extended_0.166.0 hugo v0.166.0-78400b4de8adc99273d3e674ed6c4d14c3bdf669+extended linux/amd64 Pages │ 13 Total in 137 ms ✨ Success! Uploaded 32 files (1.64 sec) 3. Check the first deployment # In the dashboard, creating the project starts the first build; through the API you start it yourself (see below). The first build here took about 50 seconds from \u0026ldquo;queued\u0026rdquo; to \u0026ldquo;deploy success\u0026rdquo;, most of it cloning. Open \u0026lt;project\u0026gt;.pages.dev and check it returns 200 before touching the domain.\n4. Add the custom domain # First make sure no old record sends the domain somewhere else. Mine still pointed at GitHub Pages, and a stranger had claimed it: part 2 is that story and its fix.\nThen add the apex and www under the project\u0026rsquo;s Custom domains. In the dashboard, Cloudflare also writes the DNS records when the zone is in the same account. They end up as:\nName Type Target Proxy vinhdata.com CNAME vinhdata.pages.dev on www CNAME vinhdata.pages.dev on A CNAME at the apex works on Cloudflare because it is flattened into addresses. For about 40 seconds the domain answered 522 while Pages activated it; then 200.\nDoing it through the API # Two things the dashboard does for you did not happen through the API, and the token needs two permissions:\nThrough the API What to do no first build after creating the project start a deployment of main, or push no DNS records after adding a custom domain create the two CNAMEs above yourself token permissions Account · Cloudflare Pages · Edit, and Zone · DNS · Edit on the domain\u0026rsquo;s zone (\u0026ldquo;Pages Write\u0026rdquo; and \u0026ldquo;DNS Write\u0026rdquo; in Cloudflare\u0026rsquo;s API reference) My first token had neither permission: it answered \u0026ldquo;Authentication error\u0026rdquo; on DNS and on Pages, while /user/tokens/verify still said \u0026ldquo;active\u0026rdquo;. That endpoint only proves the token is valid; call the endpoints you need to prove it is allowed.\nWhat to check # \u0026lt;project\u0026gt;.pages.dev returns 200 before you add the domain. The build log shows the submodule commit and +extended. dig +short \u0026lt;your-domain\u0026gt; and dig +short www.\u0026lt;your-domain\u0026gt; return Cloudflare addresses. The page source\u0026rsquo;s canonical link uses your domain, not pages.dev. Sources # Checked 2026-10-12.\nCloudflare Pages: Deploy a Hugo site Cloudflare Pages: Git integration, GitHub integration and troubleshooting Cloudflare Pages: Build image Hugo: module configuration (the extended check, disabled since 0.153.2) Cloudflare Pages: Custom domains Cloudflare DNS: CNAME flattening Cloudflare API: token permissions and create a Pages deployment ","date":"11 October 2026","externalUrl":null,"permalink":"/writing/2026/deploy-hugo-private-repo-cloudflare-pages/","section":"Writing","summary":"A private repo with a theme submodule, live on Cloudflare Pages and a custom domain: four hand-offs, about 50 s to deploy and 40 s more for the domain.","title":"Deploying Hugo from a private GitHub repo to Cloudflare Pages","type":"writing"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/dns/","section":"Tags","summary":"","title":"DNS","type":"tags"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/github/","section":"Tags","summary":"","title":"GitHub","type":"tags"},{"content":"Practical write-ups from building and running data platforms: how to set things up, why they break, and what the numbers say. Starting with how this site itself is deployed.\nStart here # How-to\nSet something up, with the traps on the way Deep dive\nHow a piece of the stack behaves underneath Series # Hugo on Cloudflare Pages: a private repo deployed to a custom domain, and the dangling DNS record that let a stranger use that domain first.\nBrowse by tag: Platform · Cloudflare · GitHub · Hugo · DNS · all tags\n","date":"11 October 2026","externalUrl":null,"permalink":"/","section":"Hands-on notes on data platforms","summary":"Practical write-ups from building and running data platforms: how to set things up, why they break, and what the numbers say. Starting with how this site itself is deployed.\nStart here # How-to\nSet something up, with the traps on the way Deep dive\nHow a piece of the stack behaves underneath Series # Hugo on Cloudflare Pages: a private repo deployed to a custom domain, and the dangling DNS record that let a stranger use that domain first.\n","title":"Hands-on notes on data platforms","type":"page"},{"content":"On 2026-10-11, vinhdata.com served a stranger\u0026rsquo;s gambling page from GitHub Pages, through DNS records I had left behind. Removing the records stops it at once; verifying the domain on GitHub stops it for good. Nobody had touched my Cloudflare account or my repositories.\nThe dangling record, and the two changes that closed it. Why it works # GitHub Pages serves every custom apex domain from the same addresses, four IPv4 and four IPv6:\n185.199.108.153 2606:50c0:8000::153 185.199.109.153 2606:50c0:8001::153 185.199.110.153 2606:50c0:8002::153 185.199.111.153 2606:50c0:8003::153 When a request arrives, GitHub looks at the host name and serves whichever Pages repository has claimed it. Claiming a name means typing it into a repository\u0026rsquo;s Pages settings; a name must be unique across GitHub Pages, but nothing checks DNS ownership unless the owner has verified the domain. Three conditions together hand a domain to anyone:\nCondition My domain, until 2026-10-11 DNS points at GitHub Pages four apex A records to the IPv4 addresses above, left from an earlier plan no Pages site of the owner claims the name the site never went live on GitHub Pages the domain is not verified on the owner\u0026rsquo;s GitHub account not verified Takeovers like this are common enough that GitHub documents them and recommends verifying a domain before you even add it to a repository.\nCheck your domains # Ask the domain who answers. On 2026-10-11, before the fix, mine said:\n$ curl -sI https://vinhdata.com/ | grep -iE \u0026#39;^(HTTP|server)\u0026#39; HTTP/2 200 server: GitHub.com That worked because the stranger\u0026rsquo;s site had a certificate for the name. On a name nobody has claimed, GitHub presents its *.github.io certificate and curl stops at the TLS error, so check over plain HTTP too, headers and body: curl -si http://\u0026lt;your-domain\u0026gt;/ | grep -iE '^(HTTP|server)|GitHub Pages site'.\nYou see Meaning server: GitHub.com and a page you did not publish someone else\u0026rsquo;s repository has the name: taken over over http://: server: GitHub.com, 404, body \u0026ldquo;There isn\u0026rsquo;t a GitHub Pages site here\u0026rdquo; dangling: nobody has claimed it yet, anyone can over https://, a certificate error for *.github.io also dangling server: cloudflare the record is proxied; the header cannot tell you, read the zone the host you expect fine Then read the zone itself: any A record with the IPv4 addresses above, any AAAA record with the IPv6 ones, or any CNAME to *.github.io must belong to a Pages site you run. The same goes for every record that points at a hosted service: a CNAME to a deleted bucket, a retired app or an old project is the same problem under another name.\nClose it # Remove the dangling records. I deleted the four A records and the www CNAME that pointed at the apex. The stranger\u0026rsquo;s page was gone within a minute, and so was my domain, until the real site went live on Cloudflare Pages later that evening. Verify the domain on GitHub. In your profile Settings, not the repository\u0026rsquo;s: Pages → Add a domain. GitHub shows a TXT record named _github-pages-challenge-\u0026lt;user\u0026gt;; add it, wait until it resolves, press Verify. Verification also releases the domain from any other account\u0026rsquo;s site at once, and from then on only your repositories can publish to it. Keep the TXT record. Clean up # Report the repository through GitHub\u0026rsquo;s abuse report form. A site published from a branch keeps a CNAME file with your domain, so GitHub code search may find it, but code search covers default branches only. A site deployed through Actions needs no such file. When search finds nothing, only GitHub can tell you. Check search results. Search site:\u0026lt;your-domain\u0026gt;. Search Console\u0026rsquo;s Removals tool hides a URL for about six months; the URL drops out for good once Google recrawls it and gets a 404, which is what the stranger\u0026rsquo;s pages now return. Point the domain at the real site. For this one, part 1. What to check # curl -sI on the apex and www: the server header is the host you expect. No A, AAAA or CNAME record in the zone points at something you no longer run. Domains you own are verified on GitHub, whether or not you use GitHub Pages today. The verification TXT record is still in the zone. Sources # Checked 2026-10-12.\nGitHub Docs: About custom domains and GitHub Pages GitHub Docs: Managing a custom domain for your GitHub Pages site GitHub Docs: Verifying your custom domain for GitHub Pages GitHub Docs: Troubleshooting custom domains and GitHub Pages Google Search Console Help: Removals tool ","date":"11 October 2026","externalUrl":null,"permalink":"/writing/2026/dangling-dns-github-pages-domain/","section":"Writing","summary":"Four A records left pointing at GitHub Pages and an unverified domain let any GitHub account serve a page on vinhdata.com. How to check yours and close it.","title":"How a dangling DNS record let a stranger serve my domain","type":"writing"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/how-to/","section":"Tags","summary":"","title":"How-to","type":"tags"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/series/hugo-on-cloudflare-pages/","section":"Series","summary":"","title":"Hugo on Cloudflare Pages","type":"series"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/platform/","section":"Tags","summary":"","title":"Platform","type":"tags"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"11 October 2026","externalUrl":null,"permalink":"/writing/","section":"Writing","summary":"","title":"Writing","type":"writing"},{"content":"I\u0026rsquo;m Vinh Pham, a data platform engineer. I started in data engineering, building pipelines, and moved under them: Kubernetes clusters, infrastructure as code, and the GCP and Alibaba Cloud accounts the pipelines run on.\nThis site is where I write down what I learn: how something works one level below the docs, the numbers I measured, and the trade-offs behind a choice. Examples from real systems use illustrative values.\nE-mail phamtanvinh.me@gmail.com LinkedIn vinh-pham-444314148 ","externalUrl":null,"permalink":"/about/","section":"Hands-on notes on data platforms","summary":"I’m Vinh Pham, a data platform engineer. I started in data engineering, building pipelines, and moved under them: Kubernetes clusters, infrastructure as code, and the GCP and Alibaba Cloud accounts the pipelines run on.\n","title":"About","type":"page"}]