When to Stop Building and Start Shipping
There’s a moment in every side project where you look up from the code and realize you’ve been building for a long time without ever actually shipping anything.
That moment hit me recently with KrateCMS.
What I’ve Been Building
KrateCMS is a self-hosted Laravel CMS I’ve been working on for a while. It started as a personal tool — a place to manage content, user roles, and eventually monetization — and it’s grown into something much more substantial than I originally planned.
At this point it includes:
- A full content management system (posts, drafts, categories, visibility controls)
- Role-based access with five user roles and granular permissions
- Public user profiles with avatars, social links, and SEO-friendly URLs
- A REST API with Sanctum authentication
- Stripe billing and subscription management
- An AI chat assistant powered by GPT-4 and DALL-E 3
- n8n webhook integration for automation
- Google Tag Manager, Postmark email, and a lot more
It’s a lot. Maybe too much for where I am right now.
The Problem With “Just One More Feature”
I’ve been in a build loop. Every time I think I’m close to shipping, there’s one more thing that feels necessary. The AI chat needs proper subscription gating. The billing page needs more polish. The pricing page should really be optimized before anyone sees it.
Sound familiar?
The honest truth is that none of those things matter yet. What matters is getting real users in front of the core experience — publishing content, managing a site, sharing posts — and finding out what actually needs to be better.
I was optimizing for a perfect v1 when what I need is a useful alpha.
Rethinking the Scope
I sat down and asked myself a simple question: what does a user need to do the one thing I want them to do right now?
The answer was: register, publish posts, share their profile.
Everything else is noise at this stage. So I split the feature list into two buckets.
In for the alpha:
- Authentication (registration, login, email verification, password reset)
- Posts (create, edit, publish, drafts, categories)
- Public profiles (avatars, bio, social links)
- Role-based access for teams
- Basic admin settings
- Google Tag Manager (I need analytics from day one)
- The REST API (it’s built and working — no reason to hide it)
Deferred to v1.1+:
- AI chat assistant
- Stripe billing and subscriptions
- Members-only content gating
- The pricing page
- n8n automation workflows
That last group isn’t cut — it’s just not alpha. The code stays. The routes stay. It just won’t be front and center until I have real feedback about whether people even want it.
The Chat Feature Situation
The AI chat feature is an interesting case study in scope creep.
The original plan was to gate it behind a Pro Stripe subscription. But when I dug into the code, I found that the middleware protecting the /chat route was intentionally neutered — it only checked if a user was logged in, not whether they had a paid subscription. There was even a comment in the code explaining the decision:
“Allow all authenticated users through to /chat. Free-tier users are permitted access — message limits are enforced within the Chat Livewire component for a better user experience.”
So the gate was never actually built. And implementing it properly — with real Stripe subscription checks — would add meaningful scope to an already delayed release.
The pragmatic move: hide the chat nav links behind an admin-only check for now. The route still exists, the Livewire component still works, and admins can still test it directly. But regular users won’t see it until the subscription gate is properly built in v1.1.
Two small file edits. One less blocker.
Learning to Use GitHub Releases
This whole process made me realize I’ve been using GitHub as a code backup rather than a release management tool.
GitHub Releases let you tag a specific point in your codebase, attach release notes, and communicate clearly to testers and users what’s “officially out.” I’d never really used them before — I just merged PRs into main and let the automated deploy handle the rest.
For context: my deploy workflow fires automatically whenever a PR merges into main. It builds the frontend assets, rsyncs to Cloudways, runs migrations, and clears caches. It works well. But there was no concept of “version” — just a constant stream of commits.
Adding releases doesn’t change the deploy process at all. Code still ships when PRs merge. The release is a communication layer on top:
PR merged → main updated → GitHub Actions deploys to Cloudways
↓
(then) create a GitHub Release
to mark this as "v0.1.0-alpha"
One thing worth clarifying if you’re on Cloudways: their Git deployment UI is branch-based, not tag-based. You can’t point it at a release tag and pull. It always tracks a branch. So GitHub Releases are purely for tracking and communication — your deploy continues to follow main the same way it always did.
Starting versioning at v0.1.0-alpha feels right. It’s honest about where the product is, and it gives a clear arc: alpha → beta → v1.0.
The Actual Plan
Here’s what the next few weeks look like:
- Merge the chat nav PR — hide chat from non-admin users, close the issue
- Audit the UI — find any other billing or unfinished features surfaced in the nav
- Cut
v0.1.0-alpha— create the first GitHub Release frommain - Set up a feedback channel — a simple form linked from the app before I invite anyone
- Invite testers — a small group of people who can actually use it and tell me what’s broken
The goal isn’t polish. The goal is signal.
If you’re building something and you find yourself in the same loop I was in — just ask the question: what’s the smallest version of this that someone could actually use? Ship that. Everything else is a feature request waiting to be validated.
Built with Laravel 11, Livewire, Tailwind CSS, and too much coffee.
Let me know if you want to adjust the tone, add more technical depth, make it shorter, or change any of the details — like your actual name or blog context.