I. The Departure (Leaving the Public Square)
It began with a constraint.
GitHub Free requires public repositories. For two years, my blog’s source code — every Hugo configuration, every SCSS variable, every custom shortcode — was visible to anyone who cared to look. I had accepted this as the price of free hosting.
But the blog had grown. What started as a personal journal had become a platform for Rooted Nomadism, a resource for people relocating to Spain, a body of work I had begun to value as intellectual property.
The code was no longer just code. It was the scaffolding of a philosophy. And scaffolding should not be left exposed to the weather.
So I decided to move. Not to abandon GitHub, but to separate the public face — the published blog at salahnomad.com — from the private workshop that builds it.
The destination: Cloudflare Pages. Private repository. Global CDN. Zero cost. The journey: four hours, one compromised token, and more lessons than I expected.

II. The Compromise (A Token in the Open)
The first mistake happened in the first ten minutes.
I was copying commands from my terminal into a conversation with an AI assistant. The guide asked for a GitHub personal access token to authenticate the push.
I copied the token. I pasted it. And in that moment, I understood what I had done.
The token — forty characters beginning with ghp_ — now sat in plain text inside a persistent chat log. Not in a terminal that would scroll away. Not in a file I would delete. In a history that would remain.
My stomach dropped. I had spent fifteen years teaching others to avoid the “New Here Tax” — the €2,400 and months of stress my own carelessness cost me when I first reached Málaga. And here I was, taxing myself.
The fix was immediate: revoke the token, generate a new one with minimal permissions, store it in a password manager.
But the lesson ran deeper than the fix.
Speed is the enemy of security. I had been rushing, following a guide without reading ahead, treating the migration as a task to complete rather than a process to honor. The hammam teaches that you cannot rush the steam. I had tried to rush the departure, and the departure had caught me.

III. The Shortcut (Server-to-Server)
The second challenge was not a mistake. It was a discovery.
I tried to push the repository from my laptop to the new private repo. My upload speed was 383 KB/s. The repository was 117 MB. The push died after two minutes with an HTTP 408.
I tried again. Same result. I increased the Git buffer. Same result. I considered SSH keys — ten more minutes of setup for a problem that might not be solved.
Then the assistant suggested what I had not considered: GitHub Importer. A built-in tool that clones repositories server-to-server. No local bandwidth required.
I deleted the empty destination repo, opened github.com/new/import, and filled the form: source URL, owner, name, privacy set to private. I clicked Begin import.
Two minutes later it was done. All 3,899 commits. All 860 files. Every branch and tag, copied from GitHub’s servers to GitHub’s servers, my slow upload bypassed entirely.
The lesson was not about the tool. It was about the mindset.
Sometimes the shortest path is not the most direct one. The guide assumed a fast connection. My reality was different. When I stopped following the guide and started reading the landscape, the solution appeared.

IV. The Confusion (Workers vs Pages)
The third hurdle was a confusion of categories.
Cloudflare’s dashboard had changed. Workers and Pages now sat under a single menu. I clicked Create, selected my repository, and began the setup — until I noticed the build command: npx wrangler deploy.
That is not a Hugo command. That is a Workers command. I had been configuring a serverless function, not a static site.
At the bottom of the screen, barely visible, a link waited: “Looking to deploy Pages? Get started.”
I clicked it. The interface changed. Framework preset: Hugo. Build command: hugo --gc --minify. Output directory: public. I added the environment variables — HUGO_VERSION, HUGO_ENV, NODE_VERSION — and clicked Save and Deploy. The build succeeded. The site went live.
But the confusion had cost me twenty minutes, and it taught me this: interfaces change, documentation lags, and your judgment must be sharper than both. The dashboard was designed for the most common case, not my case. The clue was there. I simply had to be paying attention.
V. The Vulnerability (A Patch in Time)
The fourth discovery was the most important.
While we configured the build, the assistant mentioned that Hugo 0.159.2 — my version, installed in April — carried a known cross-site scripting flaw, patched in July by version 0.164.0.
I was three months behind on security.
The fix was straightforward: update Hugo locally, raise the environment variable, redeploy. But the discovery raised a heavier question: how many other patches was I missing? Node.js nearing end-of-life. Outdated dependencies. A certificate expiring in ninety days.
I had been so focused on building that I had neglected maintenance.
This is the paradox of the digital artisan. We create beautiful things, but we must also keep them sound. The hammam teaches that the scrubbing is not optional; you cannot skip the gommage and expect clean skin. You cannot skip maintenance and expect secure software.
I updated everything, and wrote one line in my notebook: security is not a feature. It is a practice.
VI. The Warnings (Listening to the Code)
The fifth lesson came from the build logs.
The new build succeeded — but not silently. The logs carried warnings: languageCode deprecated since Hugo 0.158.0. .Language.LanguageDirection deprecated. .Site.Data superseded.
Warnings are not errors. The site worked. But warnings are the code’s way of speaking to you: this will break in a future version; fix it now, while you have time.
I grepped the config and the theme and found the patterns in eleven places. I replaced them: locale, .Language.Direction, hugo.Data, .Site.Language.Locale. I tested locally. The warnings vanished. I pushed, and the Cloudflare build logs came back clean.
The lesson was not about the specific deprecations. It was about the habit.
Most developers ignore warnings because the build succeeded. But warnings are cracks in a foundation — small now, catastrophic later. The zellige artisan does not leave a tile that does not fit. He recuts it. That is the standard.

VII. The Archive (The Lamp Behind)
The sixth decision was philosophical.
What to do with the old repository, now that the code had moved to a private one? Delete it? Hide it? Or keep it public as an archive?
I chose the third.
3,899 commits. Two years of mistakes and corrections. That history deserved to exist, even if it was no longer the current version. So I wrote a README — not a generic “this repo is archived” notice, but an explanation of why the workshop remains open, and where the work now lives. It begins with a line I love: “One does not leave a house. One leaves a lamp burning inside.”
I disabled GitHub Pages. The old address now returns a 404, and the README points travelers to salahnomad.com.
You can move without erasing. You can grow without denying your past. This is the essence of Rooted Nomadism: carry your heritage not as baggage, but as a compass.

VIII. The Lesson (The Caravan Moves On)
Four hours, from first command to final deployment. But the lessons will outlast the infrastructure.
Security is a practice, not a feature. The shortest path is not always the most direct one. Interfaces lie; judgment must be sharper. Warnings are requests, not noise. And archives are not failures — they are lamps.
The assistant executed the migration. But I had to recognize the token as a mistake, the timeout as a signal, the warning as a request. This is the difference between a servant and a sage. The machine is fast, tireless, efficient. The human is the one who pays attention.
The migration succeeded not because the AI was smart, but because I was present.
The caravan has moved on. The new workshop is private, protected, faster than the old one. And somewhere behind us, in a public repository that no longer deploys, a README begins with a line from a poem.
The lamp still burns.
May you find your soil sooner than I did.
— Salah Nomad Rooted in Pedregalejo since 2021

🗺️ Continue Your Rooted Nomad Journey
This article is one thread in a larger tapestry:
- The Hammam Principle — A blueprint for building digital spaces of vulnerability and trust
- The Zellige Blueprint — A system for soulful productivity, one intentional tile at a time
- Slow AI: A Guide to Mindful Technology — How to use AI with intention and patience
- Olive Trees & Algorithms — The origin story of Rooted Nomadism
🛠️ Technical Appendix (For the Curious)
If you’re considering a similar migration, here are the key decisions and their rationale:
| Decision | Rationale |
|---|---|
| GitHub Importer over local push | Bypassed a 383 KB/s upload for a 117 MB repository |
| Cloudflare Pages over alternatives | Existing Cloudflare DNS; unlimited bandwidth on the free tier |
| Hugo 0.164.0 over 0.159.2 | Patched a known cross-site scripting vulnerability |
| Private repo + public archive | Protected the work in progress; honored the journey |
Branch protection on main | No direct pushes to production; every change via pull request |
_redirects file | Redirected .pages.dev to the custom domain; no duplicate content |
These are not universal recommendations. They are the decisions that made sense for my context, my values, and my infrastructure. Your mileage may vary.






Comments