Add blog section (4 draft posts) and social post drafts for /platform funnel
Continues the content-engine plan: /platform, the homepage link, and the small-business-infrastructure course are already live. This adds the remaining pieces — a content/blog/ section with 4 long-form posts (kept draft:true, so nothing goes live until reviewed) and a committed set of ~12 ready-to-copy social posts in drafts/social-posts.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Blog"
|
||||
description: "Notes on running production infrastructure for small businesses, one honest mistake at a time."
|
||||
---
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "How I Run Infrastructure for Five Small Businesses From One Server"
|
||||
date: 2026-08-14
|
||||
draft: true
|
||||
description: "A full walkthrough of the hub-and-spoke setup behind five real small businesses — what's shared, what's scoped per brand, and why that split is the whole point."
|
||||
---
|
||||
|
||||
Right now, one hub server runs the entire technical backend for five real
|
||||
small businesses. An author's site. A short-fiction writer's site. A
|
||||
small-batch soap shop. A literary press running open submissions. A small
|
||||
press with multiple authors. Different industries, different products,
|
||||
different customers — same infrastructure underneath, run by one person.
|
||||
|
||||
This is the long version of the story I tell on [/platform](/platform/).
|
||||
Here's what "one hub, five brands" actually looks like, mechanically.
|
||||
|
||||
## What's on the hub
|
||||
|
||||
One server runs:
|
||||
|
||||
- Mail for every domain — inbound and outbound, for every business, from one
|
||||
mail platform instead of five.
|
||||
- The CI/CD pipeline that builds and ships every site, on every push to its
|
||||
own repo.
|
||||
- The email courses and campaigns that go out under each business's own
|
||||
name and voice, from a shared engine that just happens to run five times.
|
||||
- A shared Postgres database, holding each business's data in its own scope.
|
||||
- The checkout and fulfillment system behind every book and product sale.
|
||||
- Continuous monitoring watching all of it, all the time, instead of five
|
||||
separate someones checking in occasionally.
|
||||
|
||||
Each of those five businesses would otherwise need to find, pay for, and
|
||||
separately manage their own version of every single one of those pieces —
|
||||
their own email platform, their own checkout stack, their own hosting
|
||||
provider, their own monitoring. Here, it's one system, config-scoped per
|
||||
brand, watched by one person the whole time.
|
||||
|
||||
## What "config-scoped" actually means
|
||||
|
||||
This isn't five businesses crammed into one undifferentiated system hoping
|
||||
nothing collides. Each business's site, mailing list, and checkout
|
||||
configuration are separate, brand-specific settings layered on top of the
|
||||
same underlying engine — not separate codebases someone has to keep in sync
|
||||
by hand.
|
||||
|
||||
When a real fix is needed — a security patch, a new feature, a bug fix — it
|
||||
gets written once, verified once, and it's live for every business
|
||||
immediately. When something is client-specific — their branding, their
|
||||
content, their pricing — it lives in that client's own configuration, and
|
||||
nowhere else.
|
||||
|
||||
That split is the entire value proposition: shared reliability, individual
|
||||
identity.
|
||||
|
||||
## The part that actually makes this safe
|
||||
|
||||
None of the above works if a routine, one-line change to one business's site
|
||||
could somehow reach into another's, or if "shared server" quietly meant
|
||||
"shared risk." The thing that makes consolidation safe isn't which machine
|
||||
everything runs on — it's how tightly every path between the pieces is
|
||||
scoped. That's a big enough topic on its own that it gets [its own
|
||||
post](/blog/guardrails-for-running-production-alone/).
|
||||
|
||||
## Is this the right shape for your business?
|
||||
|
||||
If you're currently paying for and separately managing a stack of vendors —
|
||||
a hosting provider, an email platform, a checkout processor, a project
|
||||
tool — that have never heard of each other, this is what the alternative
|
||||
looks like in practice: one coherent system, run by someone personally
|
||||
accountable for how the pieces fit together.
|
||||
|
||||
If that's where you are, the [5-day email course on /platform](/platform/)
|
||||
walks through why this approach holds up, one honest mistake at a time.
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: "The Guardrails That Let One Person Safely Run Production Email for Five Businesses"
|
||||
date: 2026-08-14
|
||||
draft: true
|
||||
description: "What actually happens when you're not around and something breaks — the specific, boring mechanisms that make one-person-run infrastructure safe."
|
||||
---
|
||||
|
||||
The honest question anyone should ask before trusting one person with their
|
||||
technical backend is: what happens when you're not around and something
|
||||
breaks? Not "do you have good intentions" — systems don't run on intentions.
|
||||
Here are the specific, unglamorous mechanisms that make it safe, today, in
|
||||
production, for five real businesses.
|
||||
|
||||
## A routine push can never trigger a real send
|
||||
|
||||
Every campaign content repo I run has the same rule built into the tooling
|
||||
itself, not just into a process someone has to remember: pushing content
|
||||
changes can only ever produce a *draft* campaign. There's no field, no flag,
|
||||
no config value that lets a routine content push schedule or trigger an
|
||||
actual send to real subscribers.
|
||||
|
||||
Sending for real requires a separate, deliberate command — normally
|
||||
triggered by pushing a dedicated `send/<slug>` tag, not a content commit.
|
||||
If the worst outcome of a typo'd push is "an unsent draft appeared,"
|
||||
the failure mode is designed correctly.
|
||||
|
||||
## Storage that's supposed to be private can't quietly go public
|
||||
|
||||
Every object storage bucket I run defaults to private. There's an automated
|
||||
check in CI that fails the build if a change would make one publicly
|
||||
listable — unless there's a deliberate, documented comment explaining
|
||||
exactly why, right next to the code making the change. Not a wiki policy
|
||||
somewhere. A build that won't go green until you either fix it or justify it
|
||||
in writing.
|
||||
|
||||
## One business's access can't reach another's
|
||||
|
||||
Each site I run has its own deploy key, and each key is scoped so narrowly
|
||||
it can only touch that one site's own files — nothing else on the server, no
|
||||
other business's credentials or data. If one site's repo were ever
|
||||
compromised, the blast radius stops at that one site.
|
||||
|
||||
This is worth being specific about, because "shared server" and "shared
|
||||
risk" get conflated constantly. The thing that actually determines whether
|
||||
one business's problem can become another's isn't which physical machine
|
||||
they're on — it's whether access is properly scoped. You can have that
|
||||
property, or fail to have it, on a dedicated server or a shared one.
|
||||
|
||||
## Anything touching real customer data defaults to "show me," not "do it"
|
||||
|
||||
Every ops script that could affect real subscribers — bulk name backfills,
|
||||
unsubscribes, list edits — defaults to a dry run. Actually writing a change
|
||||
requires an explicit `--apply` flag, every time, no exceptions. The safe
|
||||
path is the path of least resistance; the dangerous one takes an extra,
|
||||
deliberate step.
|
||||
|
||||
This isn't hypothetical caution. When one client's mailing list switched to
|
||||
double opt-in partway through, their earlier import batch — people who'd
|
||||
already agreed to be on the list through a real prior relationship, just not
|
||||
yet confirmed in the system — stayed exactly as `unconfirmed`, honestly
|
||||
reflecting what was actually true about them, instead of getting swept into
|
||||
a clean-looking but false "confirmed" count. Boring and correct beat clean
|
||||
and wrong.
|
||||
|
||||
## You verify after every step, not just at the end
|
||||
|
||||
Moving a production deploy key off a root account isn't safe to do in one
|
||||
commit if you're doing it carefully. It took three: move the key to a
|
||||
dedicated account, verify the deploy still works. Fix the account's shell
|
||||
so the key actually functions, verify again. Remove the old root-level
|
||||
access, verify a final time. Three commits, three separate verifications —
|
||||
because "I'm pretty sure that worked" isn't good enough on a production
|
||||
system running mail for five businesses.
|
||||
|
||||
## None of this is exotic
|
||||
|
||||
Every mechanism above is the ordinary, unglamorous work of making the
|
||||
dangerous action harder than the safe one — done once, for the whole
|
||||
system, instead of relied on per person, per day. That's what "safe to hand
|
||||
this to someone else" actually means in practice.
|
||||
|
||||
Curious how the whole system fits together? Start at
|
||||
[/platform](/platform/).
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "Why Small Businesses Don't Need a SaaS Stack"
|
||||
date: 2026-08-14
|
||||
draft: true
|
||||
description: "The real cost of renting a different tool for every job — and what the alternative looks like once you stop assuming it's the only option."
|
||||
---
|
||||
|
||||
Ask most small businesses how their technical backend is put together, and
|
||||
you'll get some version of the same answer: a checkout processor here, a
|
||||
storefront platform there, a separate email tool, a separate hosting
|
||||
provider, maybe a separate project tracker — a different vendor for every
|
||||
job, each with its own login, its own bill, its own outage schedule, and
|
||||
none of them aware the others exist.
|
||||
|
||||
That's not a personal failing. It's the default path, because every one of
|
||||
those tools is easy to sign up for individually and hard to evaluate as a
|
||||
system. Nobody sits down and designs a five-vendor stack on purpose — it
|
||||
just accumulates, one "we needed this by Friday" decision at a time.
|
||||
|
||||
## What that actually costs
|
||||
|
||||
The sticker price of each individual tool is rarely the real cost. The real
|
||||
cost is what happens at the seams: the storefront platform doesn't know
|
||||
about the email tool's suppression list, so a customer who unsubscribed
|
||||
from one still gets marketed to by the other. The checkout processor's
|
||||
receipts don't match the bookkeeping tool's categories, so someone
|
||||
reconciles them by hand every month. Nobody owns the whole picture, because
|
||||
no single vendor is responsible for more than their own piece of it.
|
||||
|
||||
And when something breaks at a seam — a webhook silently stops firing, a
|
||||
sync job quietly drops records — there's often no single person whose job
|
||||
it is to notice, because it's not clearly any one vendor's fault.
|
||||
|
||||
## The alternative isn't "bigger," it's "coherent"
|
||||
|
||||
The fix for SaaS sprawl usually gets pitched as an enterprise platform —
|
||||
Salesforce-scale software that can theoretically do everything one tool at
|
||||
a time was doing. But that trades a pile of small vendors for one giant one,
|
||||
with all the same seam problems just moved inside a single product's
|
||||
byzantine configuration, plus an enterprise price tag a small business never
|
||||
needed.
|
||||
|
||||
The actual alternative is smaller than that: one coherent system —
|
||||
hosting, email, checkout, project tracking — run as one thing by someone
|
||||
personally accountable for how the pieces fit together, instead of managed
|
||||
piecemeal across a stack of vendors who've never heard of each other. Not
|
||||
bigger. Just not fragmented.
|
||||
|
||||
That's the whole premise behind the way I run infrastructure for five real
|
||||
small businesses today — one hub, config-scoped per brand, one person
|
||||
watching the whole system instead of five partial views of five different
|
||||
pieces.
|
||||
|
||||
## If this sounds like where you are
|
||||
|
||||
If your current setup is "it mostly works, but I'm the one reconciling the
|
||||
seams by hand," it's worth seeing what the non-fragmented version actually
|
||||
looks like in practice — not in theory, but running, today, for real
|
||||
businesses. The [5-day email course on /platform](/platform/) walks through
|
||||
it, one honest mistake at a time.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "Template, Don't Fork: How a New Client Goes Live Without Inheriting Anyone Else's History"
|
||||
date: 2026-08-14
|
||||
draft: true
|
||||
description: "Why onboarding a new business starts from a genuinely clean slate — and why that's cheap and low-risk instead of a corner cut."
|
||||
---
|
||||
|
||||
When a new business signs on, the easy shortcut would be to copy an existing
|
||||
client's site, swap out the branding, and call it done. It's faster to set
|
||||
up. It's also a quiet liability: that new site inherits the old one's full
|
||||
git history, whatever secrets or config might have leaked into old commits,
|
||||
and a codebase shaped by decisions made for a completely different business.
|
||||
|
||||
Every new site I bring on instead starts from a template, generated fresh —
|
||||
not forked, not cloned. Zero inherited commit history. Zero leftover config
|
||||
from whoever came before. The skeleton — the site structure, the shared
|
||||
tooling — is identical across every client, because it's the proven,
|
||||
already-working shape. What diverges from day one is content, branding, and
|
||||
the client's own secrets, nothing else.
|
||||
|
||||
## Why this matters more than it sounds like it should
|
||||
|
||||
Onboarding risk is usually invisible until it isn't. A forked repo carries
|
||||
forward every decision — good or bad — that was ever made in the original
|
||||
site's history, plus whatever accumulated in there that nobody remembers is
|
||||
still there. A generated repo carries forward none of that. The new
|
||||
business gets exactly the current, proven template, with a clean history
|
||||
that's theirs alone from commit one.
|
||||
|
||||
It also means a shared tool can be pinned to a specific, tested version per
|
||||
client, rolled out deliberately, one business at a time — instead of every
|
||||
site silently inheriting whatever the "source" repo happened to be running
|
||||
on the day it was forked.
|
||||
|
||||
## What actually diverges between clients
|
||||
|
||||
Looking across the five real businesses running on this setup, what's
|
||||
different from one to the next is narrow and deliberate: their content,
|
||||
their branding, their pricing, their own credentials for their own mailing
|
||||
list. What's identical is the skeleton underneath — the site structure, the
|
||||
shared engines for email and checkout, the deployment pipeline.
|
||||
|
||||
That's what makes bringing on a new business cheap and low-risk instead of a
|
||||
bespoke project every time: there's no "figuring out this client's
|
||||
infrastructure from scratch." There's a proven shape, and a short list of
|
||||
things that actually need to be decided for them specifically.
|
||||
|
||||
## What this means if you're considering this for your own business
|
||||
|
||||
Onboarding you doesn't mean untangling your business from someone else's
|
||||
history, and it doesn't mean building your infrastructure from a blank
|
||||
page either. It means slotting your business into a shape that's already
|
||||
running, safely, for others — with your own data and credentials scoped
|
||||
to you, and nothing carried over that shouldn't be.
|
||||
|
||||
More on how the whole system fits together: [/platform](/platform/).
|
||||
@@ -0,0 +1,219 @@
|
||||
# Social post drafts — /platform launch series
|
||||
|
||||
Ready-to-copy LinkedIn/X posts. Suggested cadence: ~2-3x/week, alternating
|
||||
technical-guardrail posts (credibility with technical referral partners) and
|
||||
human/outcome posts (credibility with small-business owners). Posting order
|
||||
below follows that alternation. Each links to https://reground.org/platform/.
|
||||
|
||||
All claims are sourced from real commits/incidents in this workspace —
|
||||
sourced-from note under each post for your own reference; drop those notes
|
||||
before posting.
|
||||
|
||||
---
|
||||
|
||||
## 1. [Guardrail] The CI check that blocks a public bucket
|
||||
|
||||
I run object storage for five small businesses. Every one of those buckets
|
||||
defaults to private — and there's a CI check that fails the build if a change
|
||||
would make one public, unless there's a comment explaining exactly why.
|
||||
|
||||
Not a policy. Not a reminder in a wiki somewhere. A build that won't go green
|
||||
until you either fix it or justify it in writing, right next to the code that
|
||||
does it.
|
||||
|
||||
That's the difference between "we're careful about this" and "this system
|
||||
will not let you be careless about this by accident."
|
||||
|
||||
More on how I run infra for five real businesses off one server →
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: reground-infrastructure .gitea/workflows/tf-lint.yml + scripts/check-public-buckets.sh, ACL gate on every linode_object_storage_bucket)*
|
||||
|
||||
---
|
||||
|
||||
## 2. [Human] Meet the five businesses
|
||||
|
||||
Right now, one hub server runs the hosting, email, and checkout for:
|
||||
|
||||
- An author's site
|
||||
- A short-fiction writer's site
|
||||
- A small-batch soap shop
|
||||
- A literary press running open submissions
|
||||
- A small press with multiple authors
|
||||
|
||||
Same infrastructure underneath. Completely different businesses on top. None
|
||||
of them think about servers, deploy keys, or database backups — because
|
||||
that's the whole point.
|
||||
|
||||
Curious what "one hub, five brands" actually looks like under the hood?
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: reground-site content/platform.md client description; kept anonymized to match how the live page describes them)*
|
||||
|
||||
---
|
||||
|
||||
## 3. [Guardrail] A `git push` can never send an email
|
||||
|
||||
Every content repo I run campaigns out of has the same rule baked into the
|
||||
tooling: pushing to the repo can only ever produce a *draft* campaign. There
|
||||
is no field, no flag, no config value that lets a routine content push
|
||||
schedule or trigger a real send.
|
||||
|
||||
Sending for real is a separate, deliberate command. Always. On purpose.
|
||||
|
||||
If the worst that can happen from a typo'd commit is "an unsent draft
|
||||
appeared," you've designed the failure mode correctly.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: eec-campaigns internal/campaign/send.go — Send() only transitions draft/paused → running; sync never touches status)*
|
||||
|
||||
---
|
||||
|
||||
## 4. [Human] The soap maker doesn't know what a deploy key is
|
||||
|
||||
And that's exactly how it should be.
|
||||
|
||||
One of the five businesses I run infrastructure for makes small-batch
|
||||
cold-processed soap. She doesn't need to know what a deploy key is, what
|
||||
Postgres is, or how her checkout flow is hosted. She needs her site up, her
|
||||
orders processed, and her email list intact.
|
||||
|
||||
The technical decisions underneath are mine to be accountable for, not hers
|
||||
to worry about. That's the actual service.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
---
|
||||
|
||||
## 5. [Guardrail] Scoped keys, not shared trust
|
||||
|
||||
Each of the five sites I run has its own deploy key, and each key can only
|
||||
touch that one site's files — nothing else on the server. It's enforced by
|
||||
the key itself, not by a policy someone has to remember to follow.
|
||||
|
||||
So if one site's repo were ever compromised, the blast radius is that one
|
||||
site. Not the other four. Not the shared database. Not the mail server.
|
||||
|
||||
"Scoped" isn't a nice-to-have when you're running multiple businesses from
|
||||
one place — it's the entire reason it's safe to do at all.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: reground-infrastructure per-site forced-command rrsync deploy keys)*
|
||||
|
||||
---
|
||||
|
||||
## 6. [Human] Why a literary press's open-submissions inbox matters to me
|
||||
|
||||
One of the businesses I run infra for is a literary press that runs open
|
||||
submissions — meaning strangers' work lands in their system every week,
|
||||
sight unseen, from writers hoping someone reads it.
|
||||
|
||||
That's a real trust relationship on both ends: the press trusting their
|
||||
infrastructure won't lose or leak someone's submission, and the writer
|
||||
trusting the press with something they wrote. I don't get to see the
|
||||
manuscripts. I just make sure the plumbing holds.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
---
|
||||
|
||||
## 7. [Guardrail] Verify after every step, not just at the end
|
||||
|
||||
Moving a production deploy key off the root account isn't a one-commit
|
||||
change if you're doing it carefully. I did it in three: move the key to a
|
||||
dedicated account, verify the deploy still works. Fix the account's shell
|
||||
from a locked-down nologin to bash so the key actually functions, verify
|
||||
again. Remove the old root-level key, verify a final time.
|
||||
|
||||
Three commits, three separate verifications, because "I'm pretty sure that
|
||||
worked" isn't good enough on a production mail server for five businesses.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: eec commits 9fa7895 / 4497e4e / b8759d0)*
|
||||
|
||||
---
|
||||
|
||||
## 8. [Human] A real incident: what "unconfirmed" protected
|
||||
|
||||
One of the five businesses I run switched their list to double opt-in
|
||||
partway through. Their earlier import batch — people who'd already agreed to
|
||||
be on the list through a real prior relationship, just not yet
|
||||
Listmonk-confirmed — could have been swept up and auto-confirmed to make the
|
||||
numbers look clean.
|
||||
|
||||
Instead, those subscribers stayed exactly as `unconfirmed`, honestly
|
||||
reflecting what was actually true about them, while the tooling was updated
|
||||
to explain why. Boring is correct. Correct beats clean.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: reground-tools commit f03c36a, 2026-07-22, Two Squirrels Press double opt-in)*
|
||||
|
||||
---
|
||||
|
||||
## 9. [Guardrail] Nothing that touches real subscriber data runs by default
|
||||
|
||||
Every ops script I use that could affect real subscribers — bulk name
|
||||
backfills, unsubscribes, list edits — defaults to "show me what this would
|
||||
do." Actually doing it requires a separate `--apply` flag, every time, no
|
||||
exceptions.
|
||||
|
||||
The safe path is the path of least resistance. The dangerous path takes an
|
||||
extra, deliberate step. That's not a coding style — it's a decision about
|
||||
what a mistake is allowed to cost.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: reground-tools backfill-names.sh / unsubscribe-subscriber.sh dry-run-by-default, --apply required)*
|
||||
|
||||
---
|
||||
|
||||
## 10. [Human] "Generate," not "fork"
|
||||
|
||||
Every new client site I bring on starts from a clean slate — zero inherited
|
||||
git history, zero leftover config from whoever came before them. Not because
|
||||
forking is technically hard, but because a new client's site shouldn't carry
|
||||
another business's commit history, secrets, or half-finished experiments
|
||||
into their own repo on day one.
|
||||
|
||||
Onboarding a new business should feel like a fresh start, because it is one.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: campaigns-template / eec-courses-template — Gitea "Generate Repo" pattern)*
|
||||
|
||||
---
|
||||
|
||||
## 11. [Guardrail] One engine, five brands, one fix
|
||||
|
||||
When RFC 8058 one-click unsubscribe needed to ship, it went out to every
|
||||
brand I run at the same time — because it lives in one shared engine, not
|
||||
five separate forks that would each need the same fix applied by hand, five
|
||||
times, with five chances to get it slightly wrong.
|
||||
|
||||
That's the actual payoff of "shared engine, per-brand config": a real fix is
|
||||
one change, verified once, live everywhere.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: eec + bookshop shared RFC 8058 one-click unsubscribe rollout)*
|
||||
|
||||
---
|
||||
|
||||
## 12. [Human] What used to be four vendors is now one afternoon
|
||||
|
||||
Before this setup, a small business selling books needed a checkout
|
||||
processor, a separate storefront platform, a separate ebook delivery
|
||||
service, and a separate email platform — four vendors, four logins, four
|
||||
bills, none of which talk to each other on their own.
|
||||
|
||||
Now it's one shared service, deployed once, configured per brand, running
|
||||
identically for every business that uses it. Same reliability, a fraction of
|
||||
the moving parts.
|
||||
|
||||
reground.org/platform
|
||||
|
||||
*(sourced: bookshop — replaces Stripe + Shopify + BookFunnel + ConvertKit-style stacking)*
|
||||
Reference in New Issue
Block a user