How to take back ownership of your website
A friend needed a website for his business. Property maintenance — the name covers the range, roofline down to the garden, hence gable2garden. He’d had the conversation most small business owners have: a few hundred up front, then a monthly fee forever, on a platform he’d never log into, for a site he couldn’t change without asking someone.
I built him something else instead. It took a few days and under ten hours of actual work. It has now been live long enough to say something useful about the shape of it.
The running cost is the domain name. Nothing else. Not “nothing else for the first year”, not “nothing else until you exceed the starter tier”. The hosting, the CDN, the TLS certificates, the DDoS protection, the build pipeline, the form handling, the backups — all of it sits inside free tiers that exist because Cloudflare’s real customers have problems several orders of magnitude larger than a local trades business.
He owns every part of it. If he never speaks to me again, nothing degrades. That was the design goal, and it’s the part worth writing down.
”If I stopped paying tomorrow, what do I walk away with?”
§1 · The shape of it
GitHub (private repo) ──► CI/CD ──► Cloudflare Pages ──► the public site
│ │
│ Worker (forms)
│ │
└──────────► R2 (backups) ◄────────────┘
└──► tee to notifications
Five moving parts. Each one is doing more work than it looks like.
§2 · A private GitHub repository is the whole website
Not a copy of the website. Not a backup of the website. The repository is the website — content, layout, images, configuration, deployment rules, documentation, all of it in one versioned tree.
That sounds like a developer’s convenience. It’s actually the ownership mechanism.
- It’s just Git. Clone it and you have a complete working copy on your laptop, including every version that has ever existed and who changed what and when. Not an export. Not a migration. The actual thing.
- Nothing is trapped in a database. No CMS with a proprietary schema, no admin panel holding your content hostage inside MySQL. Text is text.
- Every change is reviewable and reversible. Changes arrive as pull requests with a live preview URL. You look at the preview, you merge or you don’t. A bad change is one click to undo, and the undo is recorded too.
- Private is free. Unlimited private repositories on the free tier, with a monthly allowance of CI minutes far beyond what a small site consumes. The source doesn’t need to be public for the hosting to be free.
- Issues double as the job list. “Add winter gutter clearing to services”, “new photos from the Botley job” — the backlog lives next to the thing it describes.
- It’s portable by construction.
git remote addand push it to GitLab, Codeberg, a self-hosted Forgejo, or a USB stick. Git has no vendor. That is the entire point of it.
If GitHub vanished tomorrow the site would be inconvenienced for the twenty minutes it takes to point deployment somewhere else. That’s what “no lock-in” means when it’s structural rather than a bullet point on a sales page.
§3 · The content is Markdown, and that changes who can operate the site
Every page is a Markdown file. A services entry looks like this:
---
title: Gutter clearing and repair
order: 3
featured: true
---
Blocked gutters cause damp walls and rotten fascia boards. We clear, flush and
check the fall on the run, and photograph anything that needs attention before
it gets expensive.
That’s the whole format. Metadata at the top between the dashes, prose underneath. No shortcodes, no WYSIWYG editor emitting six nested <div>s to make a word bold.
Markdown matters here for a specific reason: it’s simultaneously readable by a person with no technical background and unambiguous to a machine. That combination is what makes §9 work.
§4 · Static output, and the categories of problem it deletes
The Markdown compiles to plain HTML at build time. What ships to a visitor is HTML, CSS and a small amount of JavaScript. No server executes code when someone visits.
Follow the consequences:
- There’s no CMS to hack. The commonest way a small business site gets defaced or turned into a malware host is an out-of-date plugin. There are no plugins. There’s no admin login to brute-force, because there’s no admin login.
- There’s no VM to maintain. Nothing to patch, no OS upgrades, no “your PHP version reaches end of life in 60 days”, no server bill, no 2am incident. The attack surface of a static file is the file.
- It’s fast by default. Pre-rendered HTML from a CDN edge node. That’s not an optimisation anyone paid for; it’s what’s left once you remove the parts that make sites slow. It matters more than it sounds — the real usage context is a customer on a phone, on 4G, standing in a driveway looking at a gutter.
- It’s legible to machines. Crawlers and AI assistants both get clean semantic HTML with no JavaScript wall to climb. People increasingly find a local business by asking an assistant rather than typing into a search box, and static HTML with proper structured data is the best possible format to be found in. A sitemap, JSON-LD for the business details and an
llms.txtcost an afternoon and nothing per month. - It survives neglect. A static site left alone for five years still works. A dynamic site left alone for five years is a security incident nobody has noticed yet.
§5 · CI/CD: the repository deploys itself
Cloudflare Pages connects directly to the private GitHub repo. Merge to main and the site rebuilds and deploys globally in well under a minute. Open a pull request and you get a preview deployment at its own URL — the change, live, before it’s live.
Nobody uploads anything. There’s no FTP client, no “I’ll push it up this evening”, no possibility of the live site and the source drifting apart. The deployed site is a pure function of the repository, which is what makes the repository genuinely the source of truth rather than merely claiming to be.
The pipeline also runs the quality gates, and that’s where cheap stops being a synonym for sloppy:
- Lighthouse on every pull request. Performance, accessibility, best practices, SEO — scored automatically, with thresholds that fail the build on regression. You can’t accidentally ship a change that tanks the site, because the robot checks before you do.
- Accessibility enforced, not aspirational. Automated axe checks catch missing alt text, insufficient contrast, broken heading order, unlabelled form controls. Automation doesn’t catch everything — you still tab through it yourself and try it with a screen reader — but it catches regressions, and regressions are what actually degrade a site over years. Visually impaired people are customers. This costs nothing but the decision to care.
- Responsive layout verified at real breakpoints, not eyeballed once on a desktop monitor at launch.
- Link checking, so the site doesn’t quietly rot.
§6 · Cloudflare’s free tier, itemised
Worth being explicit, because the list is faintly absurd:
| Capability | Cost |
|---|---|
| Global CDN, unmetered bandwidth | Free |
| TLS certificates, auto-renewed | Free |
| Unmetered DDoS protection | Free |
| Build pipeline and preview deployments | Free |
| Workers — 100k requests/day | Free |
| R2 object storage — 10 GB, no egress fees | Free |
| Turnstile (CAPTCHA replacement) | Free |
Email Routing — you@yourdomain into any inbox | Free |
| Web analytics, no cookie banner needed | Free |
| DNS | Free |
| Domain registration | At cost, ~£4–£10/year |
The domain is the only line with a real number against it, and that number moves with the TLD and — much more — with the registrar. At an at-cost registrar, which sells at registry wholesale with no markup and no renewal jump, a .co.uk is around £4 a year and a .com around £8. The same two names at a typical high-street registrar renew at anywhere from £12 to £30, because there the cheap first year is the product and the renewal is where the margin lives. Register at cost and it stays at cost, which is the only reason a figure this small survives contact with year three.
The DDoS line deserves a moment. It’s unmetered on the free plan — the same network that absorbs record-setting attacks against banks sits in front of a gutter-clearing business in Oxfordshire, for nothing. There’s no scenario where a small business outgrows this, and none where somebody knocks it over.
Free tiers move, so check current terms before building on any of them. These have been stable for years, and the strategic reason they’re free — Cloudflare wants traffic on its network — hasn’t changed.
§7 · The enquiry form: a Worker, R2, and a tee
The one genuinely dynamic thing a small business site needs is “customer fills in form, owner finds out”. This is where most builds reach for a plugin, a form SaaS at £15/month, or a PHP mail script that ends up on a blocklist.
Instead the form posts to a Cloudflare Worker. Forty lines of code, doing three things in order.
- Validate and screen. Turnstile token check, field validation, a honeypot. Bots stop here.
- Write the submission to R2. A timestamped JSON object. This is the durable record and it’s the part I care most about: the enquiry lands in the business’s own object storage, under its own account, before anything else is attempted. Notifications are best-effort. Storage is not.
- Tee to wherever the owner actually looks. The same payload fans out to any number of destinations — a transactional email API, a Telegram or WhatsApp bot, a webhook into a spreadsheet, an SMS gateway. Each destination is about five lines. If one fails the others still fire, and the R2 copy is already written.
That last property is the useful one. Notification preferences change: people move from email to Telegram, add a second person to the alerts, switch numbers. Each change is a small edit to a config file, not a migration. And because every enquiry is in R2 there’s a queryable history that doesn’t depend on nobody having deleted an email.
Cost at small-business volume: zero. 100,000 Worker requests a day is not a ceiling a local trade is going to trouble.
§8 · R2 as the backup target
Backups run on a schedule from CI: a bundle of the repository, the built site, and the accumulated form submissions, pushed to R2. Versioned, with a lifecycle rule expiring old copies.
R2’s relevant property is no egress fees. Most cloud storage is cheap to write and expensive to read, a pricing model that quietly punishes you exactly when you need your data — during a recovery, or while leaving. R2 doesn’t do that. Pulling everything out costs nothing, which is the property you want from a backup and the property you want from a vendor you might one day want rid of.
Combined with Git’s inherent distribution — every clone is a full copy — the failure modes get boring. GitHub down: site stays up, deploys pause. Cloudflare down: push the built files to anything that serves static files. Both gone: clone from the laptop, rebuild, deploy elsewhere in an afternoon.
§9 · Operating the site without being technical
This is the part that has genuinely changed in the last two years, and the reason the model works for someone who isn’t a developer.
The repository contains a project configuration file describing the site — structure, conventions, tone of voice, what may be changed and what shouldn’t, how to add a service, where images go, how to run the checks. Then Claude Code is pointed at the repository.
A change looks like this:
“Add a new service called fascia and soffit replacement. Put it after gutter clearing. Match the wording style of the others, keep it to three sentences, and use the photos I just dropped into the images folder.”
It writes the Markdown, resizes and places the images, opens a pull request. Cloudflare builds a preview. The owner opens the preview link on his phone, looks at it, and merges — or says “shorter, and lead with the damp problem” and gets a revision. Lighthouse and the accessibility checks run on that pull request regardless of who or what wrote it, so the quality floor doesn’t depend on the author knowing what a heading level is.
The same repository can be driven from Claude Code in a terminal, from Claude Desktop working against the local folder, or from Claude on the web with the GitHub connector — whichever matches how comfortable the person is. The site doesn’t know which one made the change; a pull request is a pull request.
Two things make this real rather than a demo. First, Markdown: because the content format is plain text that people and models both read natively, there’s no lossy translation between “what the owner asked for” and “what ends up in the file”. Second, the guardrails are structural — pull requests, preview URLs, CI gates, Git history. Nothing can break in a way that isn’t visible before it ships and reversible after.
Alongside that, two pages of written tutorial specific to this site, using its actual file names: change the opening hours, swap a photo, add a testimonial. The AI is the convenient path. The tutorial is the guarantee that the convenient path isn’t the only one.
§10 · Design, and how the site got built in days
I’m an engineer, not a designer, and small business sites usually look it.
The visual direction for gable2garden came out of Claude Design — an agent with a canvas and real design tools that you iterate on through conversation. Rather than picking a template and inheriting its assumptions, I described the business, its customers and the feeling the thing needed to have, then argued with what came back until it was right. Layout, type scale, colour, how the service cards behave. It’s the fastest route I’ve found from “I know what this should feel like” to “here is a coherent design system I can implement”, and it’s why the site doesn’t look like every other trades site built from the same four themes.
That plus Claude Code for the implementation is why the total was days rather than weeks — and why unfinished parts could simply ship disabled. A booking widget and a gallery weren’t ready, so they sit behind feature flags: present in the repo, invisible to visitors, one config change from live. A site with five good pages beats a perfect one that launches in November.
§11 · The ownership arithmetic
Annual running cost:
| Item | Cost |
|---|---|
| Domain, at-cost registrar | ~£4–£10 |
| Everything else | £0 |
What he owns:
- The domain, registered in his name, on his account, paid with his card.
- The GitHub organisation and repository, on his account, with me as a collaborator he can remove in two clicks.
- The Cloudflare account, with the Pages project, the Worker, the R2 buckets and the DNS. His email, his password, his billing details on file even though nothing bills.
- Every enquiry any customer has ever sent, in his own object storage.
- Backups he can download without paying an exit fee.
The test worth applying to any web arrangement, and the one this was built to pass:
If I stopped paying tomorrow, what do I walk away with?
Here the answer is everything, and nothing changes, because there was nothing to stop paying. He can hand the repository to any developer alive and they’ll be productive in an hour, because it’s a Git repo full of Markdown and a standard static site generator — not a bespoke system whose only expert is the person who built it.
§12 · Why this is future-proof rather than merely cheap
Cheap-and-brittle is easy. This is cheap because the expensive parts were removed, not because corners were cut.
”The durable pieces are Git, Markdown and static HTML. The replaceable pieces are the vendors — and each is swappable in isolation.”
Git is twenty years old and underpins essentially all software development. Markdown is plain text. HTML is the most backwards-compatible format in computing — the site will render in a browser that hasn’t been written yet.
The replaceable pieces are the vendors, and each is swappable in isolation. GitHub for any Git host. Pages for any static host. The Worker for any function runtime. R2 for any object store. Claude Code for whatever operates on repositories in three years’ time, since the interface it’s operating through is “a folder of Markdown and a Git history” — which any future tool will handle.
No layer has to be preserved for the whole to keep working. That’s the difference between sovereignty and a promise of portability, and it’s why I expect this setup to look better in five years rather than worse.
Drop me an email if you’d like to discuss how such a setup can benefit you: [email protected]