Imagine you have a name tag.
The name tag says deeka.work. You wear it every day. Friends type the name. Browsers fetch the name. In your head, the name is the website, the way “home” is a house, not a GPS coordinate that happens to currently point at a house.
Then one afternoon you move the house.
You do not move the name tag. You just… point it at a different building. And for a while, two buildings both think they are you. One of them is in Helsinki. One of them is “region Earth,” which is Cloudflare’s way of saying everywhere and therefore nowhere in particular. Your normal browser tab still visits Helsinki. An Incognito window visits Earth. You hard-refresh. Earth shrugs and hands you a 404. Two roommates who were never in the moving boxes, a trading journal called Sudden and a hobby project that belongs to my brother, are suddenly homeless, because they were living in the building, not in the name.
That afternoon is this post.
Not a runbook. A correction of a mental model I had been using since I first rented a VPS and decided that “my site” meant “that Linux box over the ocean.”
Your brain’s website vs the actual machine
When a non-web person thinks “website,” they think: a page. Words. Maybe a little cartoon.
When a slightly-too-online person thinks “website,” they think: a folder of files on a computer they SSH into.
When Hugo people think “website,” they think: markdown in, HTML out, a theme named PaperMod sitting in themes/ like a borrowed coat.
All three are incomplete. Here’s a more annoying, more accurate stack of dolls:
Doll 1: The name.
deeka.work. This is a string humans type. It is not a computer.
Doll 2: DNS. A signpost. “If anyone asks for that string, send them here.” Cloudflare was already holding this signpost for me. The arrow used to say: Hetzner, Europe.
Doll 3: The building.
A VPS. Helsinki. Slow Finder. Snap packages. A notes.txt. Empty-looking Hugo folders named common and current. Other people’s stuff. My stuff. Docker Compose that I used to start by hand like a Victorian engineer shoveling coal.
Doll 4: The Hugo project. Markdown, config, theme. The thing that looks like a blog when you build it. The whole archive of this doll, when I finally zipped it: 8.5 MB. Smaller than the emotional weight I had assigned to “migrating production.”
Doll 5: The theme’s secret inner life.
PaperMod is not “some CSS in my repo.” It is someone else’s git repository, nested inside mine, pointed at by a commit hash. I did not know this when I started talking to Cursor. I learned it because the agent wrote a .gitmodules file and I said, out loud, “I don’t know what this git module is. I’m seeing this for the first time.”
Doll 6: The roommates. Sudden. A currency converter. My brother’s project. These were never Doll 4. They just happened to share Doll 3, and Doll 1 made them feel like they were part of the same animal.
The entire “migration” was me taking Doll 4 out of Doll 3, putting Doll 4 in GitHub, asking Cloudflare Pages to build Doll 4 on every push, then rotating Doll 2’s arrow.
Doll 6 did not get a box.
Why the building was in Finland in the first place
I was already on the machine over SSH. Narrating in English. Checking whether the session was being recorded. The vibe was: okay, what’s in this folder.
The folder was all the Hugo files. Instinct #1: copy-paste them somewhere else. Instinct #2: we don’t have a local deeka folder. Instinct #3: create one. Instinct #4: wait, should we log out of SSH? Instinct #5: no, we need the folder, idiot.
I created deeka. Then I opened it in Cursor. Then I attached the remote host. There was a flicker of “is this the 211 one?” Then: yes, this SSH. Correct box.
I did not want the editor to land in the remote root. Root is how you zip / and spend the rest of the week in a support ticket with yourself. I wanted the Hugo directory, the one with the actual projects.
The connection was slow. Of course it was slow. The computer was sitting in Europe and I was sitting in India, dragging Finder windows across an ocean like it was a reasonable UI.
I practiced Helsinki. Hel-sinki. Helsinki. There is a pronunciation. I used it. This felt important at the time. In hindsight it was stalling.
The zip saga, starring a missing program
You would think “download the site” is scp. You would be a more efficient person than I was that day.
I asked the agent to zip the directory. Then my mouth issued a constraint that would have been catastrophic:
Zip only the files in the current directory and no subfolders.
Then, immediately, the correction:
No, no, no, no. We need to zip everything.
Hugo is subfolders. The theme is a subfolder. Content is a subfolder. “No subfolders” is how you ship a website that is just a lonely hugo.toml whispering in an empty room.
The command became zip -r archive.zip. Recursive. The whole doll.
Except: zip was not installed.
The server had been serving a homepage to the public internet and could not create a zip file. We installed zip. We continued. Civilization resumed.
Download to where? Downloads. Name it deeka-archive.zip. While the bytes crawled over from Helsinki, I opened GitHub to ask a question that should have been asked first:
Do I already have a repo for the home site?
I did not.
I had Pennywise, Goat, Sudden, Doodo, For Me, QuickSalt, Memories. Developer Journal. Appum. AI build. A graveyard of good names. No deeka. The homepage, the name tag itself, had never been a git repo. It had been files on a box. Doll 3 pretending to be a source of truth.
The archive arrived. 8.5 MB. I unzipped, deleted the zip, copied into the real folder with Command + Option (the macOS “duplicate this universe” chord), and threw away the empty leftover in Downloads.
Somewhere in my head a tiny flag went up: you have a backup now. Flags like that are how you later skip dumping MongoDB.
A detour I did not schedule: Sudden
Once the zip was safe, I wandered the old machine the way you wander an apartment on moving day “just to see.”
notes.txt. Needed. Snap. Inside Snap: Hugo. Inside that: not much. Then Sudden.
I paused. On the recording you can hear the pause. Sudden is a trading journal I started around November 2025. Built for me. It had a stretch where it felt like it might become A Real Project. Then around February I stopped trading, which is a very efficient way to stop using a trading journal.
Eight to ten trades. Not a career. A mood.
I still opened it. Password on the front. I did not remember the username. This is a recurring genre of problem in my life. The login worked anyway, which is the universe being briefly kind.
Dashboard: last 30 days empty, because the useful window was November to February. Then failed to fetch dashboard data. Then slow. Then dead slow. Then loading forever. Then timed out.
The stack, recovered from memory like a dream:
- Java
- MongoDB
- Docker Compose
- Frontend and backend as separate creatures
- Me, historically, toggling containers up and down by hand
I thought about installing Python. I thought about Claude remembering me. I have a physical notebook of the trades. I told myself the backup problem was therefore small, and I gave up on that instance so I could get back to Hugo.
I also noticed my brother had something on the machine. A USB converter in the room. Code location: unknown. Plan: “I should redirect requests to the other thing.” Plan quality: fog.
Doll 6 waved from the window. I waved back and kept packing Doll 4.
Git, except the theme is a parasite (affectionate)
Local folder ready. I told Cursor’s agent: git init, push to GitHub as a private repo, ignore the usual junk. Go ahead.
It tested git. It wrote a gitignore. It wrote gitmodules. I stared at gitmodules the way a medieval peasant stares at a steam engine.
Then I asked.
A submodule is a whole other git repository living inside yours. PaperMod is not a pile of files I own. My repo stores a URL and a commit pointer. Clone without --recurse-submodules and your theme is a polite empty directory. Update PaperMod later by updating the pointer. Edit PaperMod? Commit inside the submodule first. You probably cannot push to the real PaperMod, because it is not yours. Fork or don’t touch. The parent repo never “contains” those files the way it contains your posts. It only records which commit of that other universe we are currently standing on.
My brain, immediately: I could do this for the frontend and backend of the expense tracker.
The agent’s brain, immediately: you could, and you would hate it. Same human owns both sides. Changes land together. You want one PR, one conversation, one “did I forget to deploy the API” panic. Monorepo. I agreed. I am not submodule-ing the expense tracker. Some lessons you take as souvenirs. Some you take as restraining orders.
I added the new private repo to GitHub Desktop. Initial commit visible. Things added. The homepage was, for the first time, a project instead of a directory on a rented computer in a city I had just learned to pronounce.
Cloudflare, and the button that is not the button
Now the name tag needed a new building.
I went looking at Cloudflare Pages. The pitch is almost aggressively simple: you push git, they build, they sprinkle the result across the planet. Hugo is a first-class preset. hugo as the build command. public as the output. No environment variables. This is not an app with secrets. This is markdown that wants to be HTML.
I already had a Cloudflare account. I already had a Worker. The dashboard said Workers and Pages like they were roommates. I created an application. I almost made a Worker named something like deekarthickworks. I searched repos. Wrong name. The repo is deeka. Private.
Question: can Pages even see a private repo?
ChatGPT: yes. You do not have to undress the repo for the internet. You authorize the Cloudflare GitHub app and you deploy.
The Pages option sat under Create Worker, which is a design choice I will never emotionally recover from. I selected Pages. Repo still didn’t match. Configure access. GitHub tried to notify me. I did not see the notification. Retry. Still nothing. Password login. Permissions: not “all repositories,” only selected ones. Saved. deeka appeared like it had been there the whole time, waiting for me to stop creating Workers.
Framework preset: Hugo. Save and deploy. Fast in the way that makes you suspicious.
Clone succeeded. Build succeeded. Deploy succeeded. Region Earth.
The Pages URL timed out once. DNS stretching its legs. Then I asked for the custom domain, the actual name tag.
Two buildings, one name, a 48-hour threat
This is the part where Doll 2 gets violent.
deeka.work already pointed at Hetzner. Cloudflare already owned that mapping. If I typed the same domain into Pages, was I replacing the arrow, or starting a knife fight between two origins that both answer to the same string?
I typed it anyway.
Cloudflare said: replace the existing record with the Pages record. Activate. Then the legal-feeling sentence:
It may take up to 48 hours for DNS records to update.
I said “bro” at my laptop. Status: initializing. I do not budget two days of identity crisis for a CNAME. I listed the leftover animals while I waited:
The currency converter still lived on the old instance. It needs to be its own app on its own subdomain. Dev logs should publish onto the same domain. Those were the known missing pieces. Sudden and my brother’s project were still in the “I’ll deal with it” pile, which is not a pile. It is a trapdoor.
Then Cloudflare asked me to complete DNS setup by logging into my DNS provider.
Cloudflare. You are the DNS provider.
The site loaded. I saw Cloudflare headers. I still could not tell if I was looking at Pages or at Hetzner wearing Cloudflare as a coat. I pinged from a terminal. I asked the agent: confirm the deployed URL is actually new. Troubleshoot. Tell me if we’re on the new thing.
The agent worked. My eyes still disagreed with themselves.
The Incognito universe
Here is a rule I will now embroider on a pillow:
A browser tab that has known your site since the Hetzner era is not a scientific instrument.
Normal tab: Helsinki. Old building. The past, still rendered, still convincing.
Incognito: Pages. New building. The future, also convincing.
Same name tag. Two bodies.
I hard-refreshed the loyal tab to force it to join the future.
404.
The future does not include whatever was not in the 8.5 MB zip. Hugo on Pages serves public/. It does not serve a Java trading journal, a MongoDB that times out, Docker Compose I used to start by hand, or my brother’s project. Those were processes on Doll 3. I rotated Doll 2 to Doll 4’s new host and called it a migration. The 404 was the building’s way of saying: you moved the blog. You did not move the roommates.
I told myself I’d message my brother. Shouldn’t be an issue.
The rest of the site, the other sections, the long posts, the tabs that were actually Hugo, loaded clean. The name tag still worked, as long as you asked it for things that had been in the moving boxes.
So: homepage on Cloudflare Pages. Currency converter still needing a subdomain. Dev logs still a promise. Sudden and the brother-project: vacated without a forwarding address. Me: saying “this is done” in the same breath as listing things that are not done.
That is the most honest definition of a hobby migration I have.
The picture I wish I’d drawn on a whiteboard first
Not a before/after chart. A social graph.
deeka.work was never married to Hugo. It was married to whatever answered on that VPS.
Hugo was one tenant. Sudden was another: Java, Mongo, Compose, eight trades, a dashboard that timed out when I got curious. The currency converter was another. My brother was another. Snap’s Hugo folders were empty cousins. notes.txt was a roommate who doesn’t pay rent but knows secrets.
GitHub was the first time Hugo got its own lease.
Cloudflare Pages was the first time “deploy” meant “someone else runs hugo when I push,” instead of “the files are already on the server because that’s where I edited them.”
Submodules were the first time I understood that PaperMod had been a guest with its own passport.
Incognito was the first time I understood that my old tab was a third building, a cache-shaped museum of Helsinki.
The 404 was not Cloudflare failing. It was me succeeding at the wrong zoom level. I successfully moved Doll 4. I accidentally evicted Doll 6.
Helsinki is still there, presumably. Docker still timeout-able. Finder still slow. The zip is gone from Downloads; the bits live in a private repo; the edge lives on Earth.
The name tag looks the same in the address bar.
That was always the trick. The name tag was never the house.