<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Backend readieness and ownership]]></title><description><![CDATA[Backend readieness and ownership]]></description><link>https://crescodb.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 20:01:33 GMT</lastBuildDate><atom:link href="https://crescodb.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Seven things I check before running Postgres on a server I own]]></title><description><![CDATA[Self-hosting Postgres is not hard. Skipping one of these seven is. I build CrescoDB, a backend you deploy to a server you already rent, so I spend a lot of…]]></description><link>https://crescodb.hashnode.dev/seven-things-i-check-before-running-postgres-on-a-server-i-own</link><guid isPermaLink="true">https://crescodb.hashnode.dev/seven-things-i-check-before-running-postgres-on-a-server-i-own</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[self hosting]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Thu, 29 Oct 2026 09:28:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abb01f3ad512977acd0dd66/e5c9ebc4-93b8-49d2-a87d-7b735b544871.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Self-hosting Postgres is not hard. Skipping one of these seven is.</p>
<p>I build CrescoDB, a backend you deploy to a server you already rent, so I spend a lot of time thinking about what goes wrong on self-hosted setups. The failures are almost never clever. They are the same seven things, skipped once.</p>
<h2>1. Something that restarts it at boot</h2>
<p>Postgres will run for weeks and then not come back after a reboot, because the service was never enabled. Enable the database and the app, then reboot the box and watch them return.</p>
<pre><code class="language-bash">sudo systemctl enable postgresql
sudo systemctl enable your-app
sudo reboot
</code></pre>
<p>If you have not rebooted, you do not know. A service that is running right now has told you nothing about whether it starts on its own.</p>
<p>This is the habit I am trying to build into CrescoDB: a deploy is not finished when the app answers once, it is finished when the whole stack has shown it comes back after a restart.</p>
<h2>2. A connection pool, before you think you need one</h2>
<p>Postgres starts a process for every connection. Give each app instance its own pool and the total multiplies faster than you expect, usually just after a traffic bump or a second instance.</p>
<p>PgBouncer in transaction mode, placed in front of the database, keeps the real connection count small. One caveat: transaction mode does not play well with session-level features such as advisory locks or some prepared statement setups, so check your driver before you switch.</p>
<p>Watch connections, not CPU. Running out of connections looks like a mystery outage while the CPU graph stays calm.</p>
<h2>3. A restore, not a backup</h2>
<p>A dump file is a hypothesis. The only proof is restoring it into a scratch database and counting rows against the source, on a schedule.</p>
<pre><code class="language-bash">createdb scratch_check
pg_restore --no-owner -d scratch_check /backups/latest.dump
psql -d scratch_check -c "SELECT count(*) FROM your_main_table;"
</code></pre>
<p>If that restore has never run, you do not have a backup. You have a file. I wrote about this in more detail in <a href="https://dev.to/the_saint_dac63e343ee8704/a-backup-youve-never-restored-is-just-a-rumour-oil">A backup you've never restored is just a rumour</a>.</p>
<h2>4. Certificates that renew themselves, and a database that is not public</h2>
<p>Two separate checks that fail in the same quiet way.</p>
<p>For certificates, use something that renews on its own, such as certbot with a timer or a proxy like Caddy that handles it, and check that the renewal actually ran, not just that it was set up.</p>
<p>For the database, bind it to localhost or a private address:</p>
<pre><code>listen_addresses = 'localhost'
</code></pre>
<p>Port 5432 open to the internet gets found. It is not a question of if.</p>
<h2>5. Migrations in one direction</h2>
<p>Forward only, applied as part of the deploy, recorded somewhere you can read. The day someone edits a table by hand in production, your migrations stop describing your database, and every later deploy is a guess.</p>
<p>If a change turns out wrong, write a new migration that fixes it. Do not reach into the live schema.</p>
<h2>6. Alerts on the boring numbers</h2>
<p>Disk, connections, failed backups, and whether the app answers. That is the short list.</p>
<p>CPU graphs feel productive and tell you very little. Disk is the one that takes you down without warning, because it fills slowly and then everything stops at once.</p>
<h2>7. Log rotation</h2>
<p>A chatty app fills the disk with logs, and then Postgres stops accepting writes. Set up logrotate, or cap the journal, before you need it:</p>
<pre><code class="language-bash">sudo journalctl --vacuum-size=500M
</code></pre>
<p>For a permanent cap, set <code>SystemMaxUse</code> in the journald config.</p>
<h2>The short version</h2>
<p>None of this is complicated. All of it is easy to skip on a Friday afternoon, and each one costs a weekend you do not get back.</p>
<p>If you only do one thing today, restore your last backup into a scratch database and count the rows. That is the one everyone skips.</p>
<p>If you want to see how I am approaching this for deploys to your own server, CrescoDB is at crescodb.com.</p>
]]></content:encoded></item><item><title><![CDATA[A backup you've never restored is just a rumour]]></title><description><![CDATA[I have 24 users and zero of them have deployed. If one of them deploys tomorrow and their server dies on Thursday, the only thing standing between them and…]]></description><link>https://crescodb.hashnode.dev/a-backup-you-ve-never-restored-is-just-a-rumour</link><guid isPermaLink="true">https://crescodb.hashnode.dev/a-backup-you-ve-never-restored-is-just-a-rumour</guid><category><![CDATA[backups]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Tue, 27 Oct 2026 21:21:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abb01f3ad512977acd0dd66/9722026f-8bb4-45e4-a5a4-efac5ad69e02.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have 24 users and zero of them have deployed. If one of them deploys tomorrow and their server dies on Thursday, the only thing standing between them and a lost week is a backup I have never opened.</p>
<p>That sentence bothered me enough to change how I think about backups. Here is where I landed.</p>
<h2>Everyone has a backup job. Almost nobody has a restore.</h2>
<p>Ask a room of developers whether their database is backed up and most hands go up. Ask how many have restored one of those backups, start to finish, and the hands mostly come down.</p>
<p>A backup job that exits with code 0 tells you a file was written. It does not tell you the file is complete, that it matches the schema you are running today, or that it will load on a fresh machine at 2am. Until you have restored it, you are trusting a file you have never opened. That is a rumour with a timestamp.</p>
<h2>What a real test looks like</h2>
<p>A real test is short to describe:</p>
<ol>
<li>Restore the backup into a scratch database that nothing else uses.</li>
<li>Query it.</li>
</ol>
<p>The second step is the one people skip. A restore that finishes without errors can still hand you a database with missing rows, an empty table, or a sequence that restarted at 1. A backup that restores quietly with missing data is worse than no backup, because you will believe it.</p>
<p>So the check is not "did the restore command succeed". It is "do the numbers look like production". Row counts on your main tables, the newest timestamp in a table that gets written constantly, and one real query your app depends on. If those come back right, you have evidence. If you only checked the exit code, you have a hope.</p>
<h2>The loop I run</h2>
<p>This is the loop CrescoDB uses for apps deployed to a user's own server:</p>
<ol>
<li>A nightly dump is taken on the server.</li>
<li>That dump is restored into a scratch database on the same server and checked.</li>
<li>The dump is copied off the box to R2.</li>
<li>It is pulled back down from R2 and restored again somewhere else.</li>
</ol>
<p>To be clear about where the data lives: backups sit in R2 as well as on the user's server. Your data is on your own machine, and a copy of the backup also lives in object storage. I would rather say that plainly than let anyone assume the backup never leaves the box.</p>
<p>The second restore is the point of step four. Restoring on the same server proves the dump is readable. Restoring a copy that came back from storage, on a different machine, proves the copy survived the trip and that you can recover when the original server is gone, which is the case that actually matters.</p>
<p>Automatic backups and scheduled restore checks are part of the paid plans, not the free tier.</p>
<h2>What only shows up on restore</h2>
<p>Backups tend to fail in quiet, boring ways. These are the ones worth checking for, and what to do about each:</p>
<ul>
<li><strong>Missing roles or ownership.</strong> A dump may reference database roles that do not exist on the new machine, so objects fail to restore or end up owned by the wrong user. Create the roles first, or dump with ownership handled deliberately.</li>
<li><strong>Extensions.</strong> If your schema needs an extension and the new server does not have it installed, parts of the restore fail. Keep a list of required extensions next to the backup job.</li>
<li><strong>Version mismatch.</strong> Restoring a dump from a newer major version into an older server often does not work. Keep the restore target on the same major version, or newer.</li>
<li><strong>Truncated files.</strong> A dump cut short by a full disk or a killed process can look like a normal file. Check the size against previous nights and let the restore fail loudly if the file is incomplete.</li>
<li><strong>Sequences and counters.</strong> Row data can come back while sequences reset, which breaks the next insert. Test an insert after restoring, not just a select.</li>
<li><strong>Permissions on the backup itself.</strong> A backup readable by the wrong users is its own problem. Check who can read the file, and treat it like production data, because it is.</li>
</ul>
<p>None of this is exotic. It simply never gets discovered until someone restores, which is the whole argument for restoring on a schedule instead of during an outage.</p>
<h2>Test your own tonight</h2>
<p>Three commands, using Postgres as the example. Swap in your own names and paths.</p>
<p>Take a dump:</p>
<pre><code class="language-bash">pg_dump -Fc -f /tmp/backup.dump your_database
</code></pre>
<p>Restore it into a scratch database:</p>
<pre><code class="language-bash">createdb scratch_check &amp;&amp; pg_restore --no-owner -d scratch_check /tmp/backup.dump
</code></pre>
<p>Compare it against production:</p>
<pre><code class="language-bash">psql -d scratch_check -c "SELECT count(*) FROM your_main_table;"
</code></pre>
<p>Run the same count against your live database. If the numbers match and a recent row is present, you now know something you did not know this morning. Drop the scratch database when you are done:</p>
<pre><code class="language-bash">dropdb scratch_check
</code></pre>
<p>Put those four lines in a script, run it from cron, and make it complain when the counts disagree. That is most of the work.</p>
<h2>The short version</h2>
<p>A backup is only a backup after it has been restored and queried. Do it into a scratch database, check real numbers, and do it again from a copy that came back from somewhere else. If you want to see how this fits into deploying a backend to your own server, CrescoDB is at <a href="http://crescodb.com">crescodb.com</a>, and the free audit page is at <a href="http://crescodb.com/audit">crescodb.com/audit</a>.</p>
<p>And if you have a backup job running right now, go restore last night's. I would like to hear what you find.</p>
]]></content:encoded></item><item><title><![CDATA[Beyond Deployment: What's Next for CrescoDB, Plus a Clearer Path to Getting Started]]></title><description><![CDATA[We're preparing an update focused on production monitoring and operational visibility. We're also addressing the confusing parts of our onboarding…]]></description><link>https://crescodb.hashnode.dev/beyond-deployment-what-s-next-for-crescodb-plus-a-clearer-path-to-getting-started</link><guid isPermaLink="true">https://crescodb.hashnode.dev/beyond-deployment-what-s-next-for-crescodb-plus-a-clearer-path-to-getting-started</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Tue, 20 Oct 2026 11:04:34 GMT</pubDate><content:encoded><![CDATA[<p>Beyond Deployment: What's Next for CrescoDB, Plus a Clearer Path to Getting Started Building a backend is only part of the work. You define your data, build your API, connect your application, test it, and eventually deploy it. Your application is live. Your users can access it. Everything appears to be working. Then something breaks. A route begins returning server errors. A query becomes unexpectedly slow. Your server starts running out of disk space. Your application stops responding, and you don't notice until someone reports it. The problem isn't necessarily that something broke. Software breaks. The problem is finding out what happened, identifying where it happened, and getting enough information to fix it. That's the next part of the developer experience we're working on at CrescoDB. Our upcoming update focuses on production monitoring and the operational visibility developers need after deployment. We're also working to make CrescoDB's own getting-started experience clearer, because we've made that journey more confusing than it should be. Here's what's changing, why it matters, and how to get a project running in the meantime.</p>
<ol>
<li>The next stage: understanding what happens after deployment We've been building CrescoDB around the backend workflow: creating your data models, generating an API, adding authentication, verifying access rules, and deploying to infrastructure you control. But a backend doesn't stop being your responsibility when it goes live. Once an application is in production, you need answers to questions like:</li>
</ol>
<ul>
<li><p>Which endpoint is returning errors?</p>
</li>
<li><p>Are requests getting slower?</p>
</li>
<li><p>Which database queries are taking the longest?</p>
</li>
<li><p>Is the server running low on memory or disk space?</p>
</li>
<li><p>Is the application actually reachable by users?</p>
</li>
<li><p>If something fails, will you know before a user has to tell you? Without monitoring, answering those questions can involve manually checking logs, connecting to servers, running commands, and trying to reconstruct what happened after the fact. We're working to make that process less painful. Application monitoring The monitoring capabilities we're preparing are designed to give developers visibility into their application's behavior, rather than simply reporting whether the application process is running. That includes information about:</p>
</li>
<li><p>Requests and errors grouped by route.</p>
</li>
<li><p>Server and client error rates.</p>
</li>
<li><p>Request latency, including p95 latency.</p>
</li>
<li><p>Slow database queries and query failures.</p>
</li>
<li><p>Application errors with useful debugging information.</p>
</li>
<li><p>Runtime indicators such as memory usage and event-loop delay. For example, imagine that a store's checkout endpoint starts returning server errors while the rest of the application continues working. A generic alert saying "your application has an error" leaves you with more questions. Knowing that POST /checkout is failing, seeing how many requests were affected, and having information to investigate the failure gives you a much more useful starting point. The goal is not to replace debugging. It's to help you find the right place to begin. Server monitoring Application health and server health are related, but they're not the same thing. An application can encounter problems because of its own code. It can also encounter problems because the machine running it is under pressure. We're expanding the visibility developers have into the infrastructure supporting their applications, including:</p>
</li>
<li><p>CPU usage and system load.</p>
</li>
<li><p>Available memory and swap.</p>
</li>
<li><p>Disk usage and partitions.</p>
</li>
<li><p>Running containers and their resource usage.</p>
</li>
<li><p>Server availability.</p>
</li>
<li><p>Operational problems that may require attention. Consider a server whose disk is nearly full. That can interfere with deployments, backups, and database operations. Finding out only when a deployment fails is much later than finding out while there's still time to act. The idea is to make those conditions visible before they turn into a larger incident. Uptime checks from outside the server Monitoring an application from inside its own process is useful, but it cannot tell you everything a visitor experiences. A server might be running while its public domain is misconfigured. A certificate might have a problem. A CDN might be returning an error. A URL might be reachable but taking too long to respond. That's why external uptime checks matter. CrescoDB's monitoring direction also includes checking public URLs from outside the server, helping identify problems involving reachability, DNS, TLS, response times, and the page being served. The objective is to see the application from more than one perspective: from inside the app, from the server underneath it, and from the outside where users encounter it. Alerts that tell you when something needs attention Monitoring is less useful if you have to keep a dashboard open all day to benefit from it. Alerts are intended to bring important problems to your attention so you can investigate them. CrescoDB's documented monitoring capabilities include email alerts, with Slack and Telegram available on paid plans. The intention is to help developers respond to meaningful failures without having to watch every metric manually. We're working toward a better operational workflow: detect a problem, understand where it is happening, investigate it, and work toward a resolution. The important distinction is that we're not trying to give developers more numbers to stare at. We're trying to make production problems easier to understand and act on. You can read more about the direction in the current CrescoDB product overview and production monitoring documentation.</p>
</li>
</ul>
<ol>
<li><p>Deployment is part of the workflow, not the end of it We've also been thinking about the steps between a working local project and a working production application. Developers have different workflows. Some prefer Git-based deployment. Others want to deploy an existing project without setting up a repository workflow first. We're working on reducing friction in that process, including ZIP-based deployment and better ways to manage disk allocation when multiple applications share a server. The goal is to make deployment more accessible without hiding the important decisions from developers. CrescoDB is built around the idea that you should be able to run your application on infrastructure you control. Your code and data shouldn't become inaccessible simply because you decide to change platforms. That means deployment should be understandable, operational problems should be easier to diagnose, and leaving the platform should remain a practical option. We're not going to pretend that every part of this workflow is finished. We're continuing to improve it.</p>
</li>
<li><p>We also owe you an apology Before talking about more features, we need to acknowledge something about the current product experience. Our homepage and onboarding instructions have been confusing, and we're sorry. We've tried to communicate a lot about CrescoDB: local development, the database and API, authentication, the dashboard, AI assistance, deployment, and the operational tools around production. Those capabilities matter. But presenting them without making the starting point obvious can leave a new developer wondering what to do first. Some of the important distinctions haven't been clear enough:</p>
</li>
</ol>
<ul>
<li><p>What you need to install.</p>
</li>
<li><p>How to create and run a local project.</p>
</li>
<li><p>Which features work locally.</p>
</li>
<li><p>When you need an account.</p>
</li>
<li><p>What changes when you want to deploy.</p>
</li>
<li><p>How to connect your project to a server you control. We should have made those steps easier to follow. A developer who wants to try CrescoDB shouldn't have to study the entire product before getting a working API. They shouldn't have to confuse local development with cloud deployment, either. That's a communication and onboarding problem, and it's our responsibility to address it. We're working to make the homepage more focused and the getting-started journey more direct. In the meantime, here's a practical guide you can follow.</p>
</li>
</ul>
<ol>
<li>How to get a CrescoDB project running You do not need to deploy to a server to begin using CrescoDB. For local development, the prerequisites are straightforward: install Node.js 20 or newer, then install CrescoDB. Step 1: Install Node.js CrescoDB requires Node.js 20 or newer. Node.js 22 LTS is the recommended starting point. Check your installed version: node --version</li>
</ol>
<p>If you need to install Node.js, use the official installer or a version manager.</p>
<p>Step 2: Install CrescoDB Install the CLI globally: npm install -g crescodb</p>
<p>You can also use npx crescodb if you prefer not to install it globally.</p>
<p>Step 3: Create a project Create a new folder for your project, move into it, and initialize CrescoDB: mkdir my-project cd my-project cresco init</p>
<p>This creates the basic project files, including the schema, configuration, and local SQLite database. Important: A new project starts empty. That is intentional. You define your own tables and data models rather than being given a pre-populated application. If you would rather start with demo tables, initialize a starter project instead: cresco init --starter</p>
<p>Use one initialization option or the other for a new project.</p>
<p>Step 4: Start the development server From your project directory, run: cresco dev</p>
<p>CrescoDB starts the local REST API and development dashboard.</p>
<p>Step 5: Open the dashboard Visit: <a href="http://localhost:3000/studio">http://localhost:3000/studio</a> From the dashboard, you can browse and edit data, design your schema, run SQL, inspect your models, and explore the project as you build it. You now have a local project to work with. You do not need a rented VPS, a cloud account, or Docker simply to reach this stage.</p>
<p>Step 6: Add your data models Your project starts empty unless you chose the starter option. You can define tables through the schema file, use the dashboard's schema editor, or describe the data you want to create through the supported AI workflow. For example, a simple blog might need models for posts and comments. A store might need products and orders. Once your schema is in place, CrescoDB can generate the corresponding API. You can then use those endpoints from your frontend or another application over HTTP.</p>
<p>Step 7: Add authentication and verify access rules When your application needs authentication, you can generate the authentication code: cresco create auth</p>
<p>CrescoDB writes editable code into your project rather than requiring you to treat authentication as an opaque black box. Before deployment, you can also run: cresco audit</p>
<p>This checks access rules against your schema and identifies critical exposure issues. CrescoDB's deployment workflow also runs the audit before preparing a deployment, refusing to proceed when it finds a critical issue unless you explicitly override it. Step 8: Deploy when you're ready Local development and production deployment are separate stages. Once your application is working locally, you can move on to deployment. CrescoDB supports deploying to a server you control, with a workflow for connecting the server, deploying the project, and configuring a domain. You don't have to do this before you can start building. For the deployment steps, use the official guide to deploying to your own server rather than trying to infer the process from the homepage. The full local quickstart is available in the CrescoDB getting-started guide. 5. What we're trying to build with CrescoDB We don't want CrescoDB to be just another way to generate an API. We want the workflow to make sense from the first local project through production:</p>
<ul>
<li><p>Build: Define your data and get a working backend.</p>
</li>
<li><p>Verify: Check access rules before deploying.</p>
</li>
<li><p>Deploy: Run your application on infrastructure you control.</p>
</li>
<li><p>Monitor: Understand application errors and server health.</p>
</li>
<li><p>Operate: Manage the ongoing work of keeping an application healthy.</p>
</li>
<li><p>Own: Retain control over your code and data. These parts are connected. A project that runs locally but is difficult to deploy has an incomplete workflow. An application that deploys successfully but provides no useful operational visibility creates a different problem. And a platform that makes it difficult to leave isn't giving developers full control. We're working on these parts together because they affect the experience of building and maintaining real software.</p>
</li>
</ul>
<ol>
<li>What's next? Tomorrow, we're sharing the next CrescoDB update, with a focus on production monitoring and the visibility developers need after deployment. We're also continuing to improve deployment workflows and address the confusion around getting started. We'll keep sharing what we're building, explaining what has changed, and listening to the developers using the product. We know that shipping features isn't the only measure of progress. The product needs to be understandable, the documentation needs to be useful, and the path from trying CrescoDB to building something real needs to be clear. We've got work to do on that, and we're taking it seriously. Thank you to everyone who has tested CrescoDB, shared feedback, reported problems, or followed the project while it's evolving. We're building this in public, including the parts we need to get better at. Stay tuned for tomorrow's update. Explore CrescoDB: CrescoDB official website Get started: CrescoDB quickstart</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Building a Startup Taught Me That Not Every Good Idea Deserves to Be Built]]></title><description><![CDATA[Why product focus, user feedback, and distribution matter more than an ever-growing feature list]]></description><link>https://crescodb.hashnode.dev/building-a-startup-taught-me-that-not-every-good-idea-deserves-to-be-built</link><guid isPermaLink="true">https://crescodb.hashnode.dev/building-a-startup-taught-me-that-not-every-good-idea-deserves-to-be-built</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Sun, 11 Oct 2026 08:59:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abb01f3ad512977acd0dd66/53a0f680-7a42-4b05-9707-ee4f9649cdcb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the easiest mistakes to make as a developer is confusing the ability to build something with a reason to build it. When you know how to code, ideas become tempting. You see a problem and immediately start thinking about the implementation. You imagine the interface, the architecture, the database schema, and all the features that could make the solution better. Before long, you're planning an entire product around an idea you haven't validated. I've been reflecting on this while building developer tools and thinking about what it actually takes to turn software into a business. The lesson is simple, although putting it into practice is harder: A good idea isn't automatically a good use of your time. The builder's advantage can become a disadvantage Developers have an advantage: we can turn ideas into working software. But that advantage can become a distraction. When something is technically possible, it's easy to prioritise the implementation over the underlying problem. You can spend days designing a flexible architecture for a feature nobody has requested. You can build an impressive dashboard before establishing whether users understand the product. You can add five integrations because they make the product look more complete, even when the existing workflow still has problems. The work feels productive because something tangible is being created. Unfortunately, technical progress and business progress aren't always the same thing. A working feature proves that you can build it. It doesn't prove that somebody needs it. Your roadmap is a set of trade-offs Every feature competes for resources. For a small startup, those resources include development time, money, attention, and the capacity to maintain what has already been shipped. Suppose you have two weeks available. You could spend them building a new integration. Alternatively, you could use that time to improve onboarding, investigate why users abandon the product, or contact potential customers. None of these options is universally correct. The point is that choosing one means delaying another. A useful roadmap should make those trade-offs explicit rather than treating every promising idea as an immediate commitment. I find it helpful to separate ideas into three categories:</p>
<ul>
<li><p>Now: Work that addresses a demonstrated problem or a critical blocker.</p>
</li>
<li><p>Next: Work supported by credible evidence but not yet urgent.</p>
</li>
<li><p>Later: Interesting possibilities that haven't earned development time. The third category is important. Putting an idea in the backlog doesn't mean rejecting it. It means acknowledging that its value hasn't yet justified the cost of implementing it. Talk to users before your assumptions become architecture One reason to validate ideas early is that assumptions become expensive when they're embedded in software. Suppose you believe developers need a particular workflow. You build the database tables, APIs, permissions, interface, and documentation around that assumption. Then you discover that users approach the problem differently. Now the cost of changing direction is much higher than it would have been during a conversation or prototype. Early feedback helps you discover those mismatches before they become architectural commitments. That doesn't mean blindly implementing every request. Users can identify their frustrations without necessarily knowing the best solution. Your job is to understand the underlying problem, investigate the evidence, and decide how to address it. The goal is not to let users design your entire roadmap. It's to stop building in isolation. Building the product isn't the entire job There's another assumption worth challenging: once the product is good enough, users will arrive. Sometimes they do. Usually, they need a reason to discover it first. People have existing workflows, familiar tools, limited attention, and little incentive to experiment with an unfamiliar product. A new product must overcome those barriers. That requires distribution. For a developer-focused startup, distribution might involve writing technical articles, contributing to relevant communities, sharing useful tools, talking directly to developers, or creating content that solves real problems. These activities can appear less satisfying than shipping a feature. They also provide information that writing code cannot provide on its own. You learn which problems attract attention, which explanations resonate, what people misunderstand, and which audiences are willing to try your product. That information can influence what you build next. In that sense, distribution isn't merely how you market a finished product. It can also be part of how you discover what the product should become. A more useful definition of progress An expanding codebase is easy to measure. So are the number of features shipped, commits made, and tasks completed. But those numbers can become misleading if they aren't connected to outcomes. For an early-stage product, I'd rather ask:</p>
</li>
<li><p>Can a new user understand what the product does?</p>
</li>
<li><p>Can that user accomplish the intended task?</p>
</li>
<li><p>Are people returning after their first experience?</p>
</li>
<li><p>What problems cause users to abandon the workflow?</p>
</li>
<li><p>Can we reach the people who would benefit from the product?</p>
</li>
<li><p>What evidence suggests our next investment will matter? These questions are harder to answer than counting features. They're also more useful when deciding where a small team should spend its next week. What this means for founders who build If you're a developer building a startup, you don't have to stop exploring ideas. Curiosity is part of what makes building enjoyable. But you need a mechanism for separating exploration from commitment. Write ideas down. Investigate the problems behind them. Talk to people who experience those problems. Test assumptions before making expensive commitments. And when a feature doesn't solve a sufficiently important problem, be willing to leave it out. The goal isn't to make your roadmap look impressive. The goal is to make your product useful enough that people have a reason to choose it. You can always expand once you've earned that opportunity. Until then, focus is a competitive advantage. A question for other builders What's one thing you've learned about building products that you wish you'd understood before starting your first serious project? I'm particularly interested in lessons about prioritisation, finding users, and knowing when a product is ready to ship.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Git Isn't the Only Way to Deploy: ZIP Deployment and Better VPS Space Allocation]]></title><description><![CDATA[Git-based deployment is useful for a reason. Your code lives in a repository, changes can be tracked, and deployments can follow a branch or commit. For a…]]></description><link>https://crescodb.hashnode.dev/git-isn-t-the-only-way-to-deploy-zip-deployment-and-better-vps-space-allocation</link><guid isPermaLink="true">https://crescodb.hashnode.dev/git-isn-t-the-only-way-to-deploy-zip-deployment-and-better-vps-space-allocation</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Fri, 09 Oct 2026 19:21:19 GMT</pubDate><content:encoded><![CDATA[<p>Git-based deployment is useful for a reason.</p>
<p>Your code lives in a repository, changes can be tracked, and deployments can follow a branch or commit. For a team shipping continuously, that workflow makes sense.</p>
<p>But not every deployment begins with a Git repository.</p>
<p>Sometimes you already have a project folder you want to ship. Sometimes you're working with an exported project, a prototype or an application that isn't connected to a deployment repository.</p>
<p>In those situations, requiring Git to be the only entry point can add unnecessary friction.</p>
<p>That's why we're adding ZIP deployment to CrescoDB.</p>
<h2>An additional way to deploy</h2>
<p>CrescoDB already supports GitHub-based deployment workflows, including deployment to your own VPS and supported managed platforms.</p>
<p>ZIP deployment adds another option: upload an application archive and use it as the starting point for deployment.</p>
<p>The important distinction is that ZIP deployment complements the existing workflow. It doesn't replace Git.</p>
<p>Git remains valuable when you need version history, code review, collaboration and repeatable deployments tied to repository changes.</p>
<p>A ZIP-based workflow is useful when the project is ready to deploy but the repository isn't the most convenient starting point.</p>
<p>The best deployment method depends on how the application is developed and maintained.</p>
<h2>The next question: where should the application run?</h2>
<p>For developers using a VPS, deployment is only one part of the problem.</p>
<p>A server has finite resources. Disk capacity, memory, CPU and network capacity all have limits.</p>
<p>As you deploy more applications onto the same server, you need to think about how those applications are organized and how available storage is allocated.</p>
<p>That's where custom server partitioning comes in.</p>
<h2>Allocate disk space across multiple apps</h2>
<p>With CrescoDB's custom partitioning, you can divide available server disk space into separate app spaces rather than treating every deployment as one undifferentiated storage area.</p>
<p>For example, imagine you have a VPS with 100GB of disk space.</p>
<p>You might choose to allocate that capacity across:</p>
<ul>
<li><p>Two app spaces</p>
</li>
<li><p>Three app spaces</p>
</li>
<li><p>Four app spaces</p>
</li>
</ul>
<p>The allocation is subject to the minimum partition size supported by the platform.</p>
<p>This gives you more control over how the server's available disk capacity is distributed across deployments.</p>
<p>Each app gets its own allocated space, helping prevent one deployment's storage from colliding with another's.</p>
<p>The exact allocation depends on your applications' needs. An app storing uploaded media may need more disk space than a small API with very little persistent data.</p>
<p>The goal is flexibility without pretending that a server has unlimited capacity. Partitioning allocates the disk space available; it doesn't create additional capacity.</p>
<h2>Why this matters for small projects</h2>
<p>A developer might have several small applications that don't each justify a separate VPS.</p>
<p>Being able to run multiple applications on one server can be practical, provided the deployment workflow and resource allocation are managed carefully.</p>
<p>Instead of immediately provisioning another server for every small project, you can decide how to organize the capacity you already have.</p>
<p>Of course, disk allocation is only one part of server management. CPU, memory, networking, security and application-level configuration still matter. Partitioning doesn't remove the need to manage those resources appropriately.</p>
<p>But it gives developers another useful control over how their deployments are arranged.</p>
<h2>Deployment is part of a larger lifecycle</h2>
<p>At CrescoDB, we're thinking beyond the act of getting code onto a server.</p>
<p>The wider workflow includes building the application, owning the infrastructure and data, verifying readiness, deploying, operating and monitoring the running system.</p>
<p>That's why our six pillars are:</p>
<p><strong>Build → Own → Verify → Deploy → Operate → Monitor</strong></p>
<p>ZIP deployment improves how developers can get an application into the workflow. Custom partitioning adds flexibility when organizing multiple applications on a VPS. Monitoring helps developers understand the health of their applications and the servers underneath them.</p>
<p>These are connected problems, not isolated features.</p>
<h2>Try the updated workflow</h2>
<p>We're adding ZIP deployment and custom server partitioning alongside CrescoDB Monitor, which brings application and server monitoring together with alerts through email, Slack and Telegram.</p>
<p>If you're building applications and managing your own infrastructure, take a look at CrescoDB:</p>
<p><a href="https://crescodb.com">https://crescodb.com</a></p>
<p>How do you currently deploy projects that aren't connected to a Git repository? And when running several applications on one VPS, how do you organize storage across them?</p>
]]></content:encoded></item><item><title><![CDATA[Application Monitoring Isn't Enough: Why You Should Monitor the Server Too]]></title><description><![CDATA[One of the easiest traps in production is assuming: “The website is responding, so everything is fine.” It isn't. An application can technically be online…]]></description><link>https://crescodb.hashnode.dev/application-monitoring-isn-t-enough-why-you-should-monitor-the-server-too</link><guid isPermaLink="true">https://crescodb.hashnode.dev/application-monitoring-isn-t-enough-why-you-should-monitor-the-server-too</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Tue, 06 Oct 2026 07:23:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abb01f3ad512977acd0dd66/36b1628d-ed00-4e6f-8d58-49f07b6703c2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the easiest traps in production is assuming:</p>
<p>“The website is responding, so everything is fine.”</p>
<p>It isn't.</p>
<p>An application can technically be online while quietly falling apart underneath.</p>
<p>Your API might still respond while CPU usage sits at 95%.</p>
<p>Your server might still accept traffic while disk space has 300 MB left.</p>
<p>Your database might be reachable while requests are taking five seconds instead of 200 milliseconds.</p>
<p>Your process might still be running while one important background worker died twenty minutes ago.</p>
<p>And sometimes the first person to tell you something is wrong is a user.</p>
<p>That's usually too late.</p>
<p>Uptime is only one signal</p>
<p>The simplest form of monitoring asks one question:</p>
<p>Is the application reachable?</p>
<p>You send a request every few seconds or minutes.</p>
<p>If it returns successfully:</p>
<p>everything looks green.</p>
<p>If it stops responding:</p>
<p>send an alert.</p>
<p>That is useful.</p>
<p>But it only detects one class of failure.</p>
<p>Production systems often degrade long before they completely disappear.</p>
<p>Imagine this:</p>
<p>Technically, the server is alive.</p>
<p>Operationally, you should probably be worried.</p>
<p>Monitoring the application</p>
<p>Application monitoring looks at what is happening inside the software.</p>
<p>Things like:</p>
<p>application crashes unhandled exceptions failed requests response time API errors runtime failures unhealthy processes failed jobs</p>
<p>Consider an API that normally responds in:</p>
<p>and gradually moves to:</p>
<p>Nothing has technically crashed.</p>
<p>But something has changed.</p>
<p>Maybe a database query became slower.</p>
<p>Maybe traffic increased.</p>
<p>Maybe a dependency is struggling.</p>
<p>Maybe memory pressure is causing garbage collection problems.</p>
<p>Monitoring gives you a chance to investigate before the application becomes unavailable.</p>
<p>Then there is the machine underneath it</p>
<p>This is the part I think developers sometimes forget.</p>
<p>Your application isn't floating in the cloud.</p>
<p>Eventually, it is running on a machine somewhere.</p>
<p>That machine has:</p>
<p>And any one of those can become the actual reason your application fails.</p>
<p>For example, your Node.js API might be perfectly written.</p>
<p>It doesn't matter if the VPS disk is full.</p>
<p>Your application can't write new files.</p>
<p>Your database may not be able to expand.</p>
<p>Logs stop being written.</p>
<p>Eventually, things start breaking in strange ways.</p>
<p>The application is the symptom.</p>
<p>The server is the cause.</p>
<p>CPU spikes matter</p>
<p>Suppose your server normally runs around:</p>
<p>Then suddenly:</p>
<p>for several minutes.</p>
<p>That might mean:</p>
<p>unexpected traffic runaway process expensive database operation infinite loop background job compromised server badly optimized code</p>
<p>You don't necessarily want an alert every time CPU touches 90% for three seconds.</p>
<p>That would become noise.</p>
<p>But sustained abnormal usage?</p>
<p>You probably want to know.</p>
<p>Something like:</p>
<p>CPU usage has remained above 90% for 5 minutes.</p>
<p>That's actionable.</p>
<p>Disk space is boring until it destroys your weekend</p>
<p>Disk monitoring might be one of the least exciting things in software.</p>
<p>Until this happens:</p>
<p>Databases need disk.</p>
<p>Logs need disk.</p>
<p>Uploads need disk.</p>
<p>Temporary files need disk.</p>
<p>Docker images need disk.</p>
<p>Backups may need disk.</p>
<p>A slowly growing log directory can quietly consume storage for weeks before anything obviously fails.</p>
<p>So instead of discovering:</p>
<p>after the damage starts, monitoring should warn you earlier:</p>
<p>The alert isn't the solution.</p>
<p>It gives you time to create one.</p>
<p>Network problems are different from downtime</p>
<p>A server can be reachable and still have terrible connectivity.</p>
<p>Maybe:</p>
<p>Your users experience the application as slow.</p>
<p>Your uptime monitor says:</p>
<p>All systems operational.</p>
<p>Both statements can technically be true.</p>
<p>This is why latency belongs beside uptime.</p>
<p>You want to understand:</p>
<p>Is the server reachable? How quickly is it responding? Has network latency changed? Are packets being dropped? Is one region behaving differently?</p>
<p>Availability and performance are related, but they are not the same thing.</p>
<p>Monitoring without alerts is just a dashboard</p>
<p>There is another mistake I've seen:</p>
<p>Beautiful monitoring dashboards.</p>
<p>Charts everywhere.</p>
<p>CPU graphs.</p>
<p>Memory graphs.</p>
<p>Latency graphs.</p>
<p>Error graphs.</p>
<p>But nobody is staring at those dashboards at 3:17 AM.</p>
<p>A monitoring system needs a way to say:</p>
<p>Something changed. You should look at this.</p>
<p>That could be:</p>
<p>depending on how the team works.</p>
<p>The important part is delivering the right alert to somebody capable of acting on it.</p>
<p>For example:</p>
<p>Production API Down</p>
<p>api.example.com has failed 3 consecutive health checks.</p>
<p>Last successful check: 02:41 UTC</p>
<p>Or:</p>
<p>Server CPU Alert</p>
<p>CPU usage on production-01 has remained above 90% for 5 minutes.</p>
<p>That is much more useful than discovering the problem because somebody posted:</p>
<p>“Is your site down?”</p>
<p>on Twitter.</p>
<p>But alerts can also become useless</p>
<p>The opposite problem is alert fatigue.</p>
<p>If your system tells you:</p>
<p>every thirty seconds, you eventually stop caring.</p>
<p>Then the important notification gets buried with everything else.</p>
<p>Good monitoring needs thresholds.</p>
<p>And often, duration.</p>
<p>Instead of:</p>
<p>CPU crossed 80%.</p>
<p>think:</p>
<p>CPU remained above 85% for five minutes.</p>
<p>Instead of:</p>
<p>One request failed.</p>
<p>think:</p>
<p>Error rate exceeded 5% over the last five minutes.</p>
<p>The goal isn't maximum notifications.</p>
<p>The goal is useful awareness.</p>
<p>Monitoring should exist at multiple layers</p>
<p>I've started thinking about production monitoring as two connected systems.</p>
<p>Layer 1 — Application</p>
<p>Watch:</p>
<p>Layer 2 — Infrastructure</p>
<p>Watch:</p>
<p>Because sometimes the application is broken.</p>
<p>Sometimes the infrastructure is broken.</p>
<p>And sometimes one is telling you something about the other.</p>
<p>Seeing both makes diagnosis much easier.</p>
<p>Monitoring shouldn't begin after something breaks</p>
<p>This is probably the most important part.</p>
<p>Teams often add monitoring after their first serious incident.</p>
<p>Something goes down.</p>
<p>Nobody notices for forty minutes.</p>
<p>Users complain.</p>
<p>The team fixes it.</p>
<p>Then someone says:</p>
<p>“We should probably add monitoring.”</p>
<p>Monitoring shouldn't be the postmortem action item.</p>
<p>It should be part of getting something into production.</p>
<p>I've increasingly started thinking about the backend lifecycle as:</p>
<p>Build → Verify → Deploy → Operate → Own → Monitor</p>
<p>Building answers:</p>
<p>Can we create it?</p>
<p>Verification asks:</p>
<p>Does it behave correctly?</p>
<p>Deployment asks:</p>
<p>Can we get it into production?</p>
<p>Operations asks:</p>
<p>Can we manage it there?</p>
<p>Ownership asks:</p>
<p>Do we retain control of what we're running?</p>
<p>And monitoring asks:</p>
<p>Will we know when something changes?</p>
<p>That last question matters more once nobody is sitting there watching the terminal.</p>
<p>This is something we're working on ourselves</p>
<p>We've been thinking about this while building CrescoDB.</p>
<p>CrescoDB already manages the production server layer for projects deployed through it, so we can see things like server availability and runtime health.</p>
<p>We're now expanding that into a more complete monitoring layer.</p>
<p>Not an attempt to recreate every observability feature from platforms built entirely around monitoring.</p>
<p>The goal is simpler:</p>
<p>Give smaller engineering teams the information they actually need to know when something is going wrong.</p>
<p>At the application level:</p>
<p>And underneath that:</p>
<p>Then push useful alerts through the channels teams are already watching:</p>
<p>Telegram, Slack and email.</p>
<p>The interesting part for us isn't adding another dashboard.</p>
<p>It's connecting application health with the infrastructure running it.</p>
<p>Because if your API goes down at the same moment the server's memory hits 98%, those two pieces of information probably belong beside each other.</p>
<p>Production is really about feedback</p>
<p>When you're developing locally, feedback is immediate.</p>
<p>Something breaks.</p>
<p>Your terminal tells you.</p>
<p>You fix it.</p>
<p>Production creates distance between the developer and the software.</p>
<p>Monitoring closes some of that distance.</p>
<p>It tells you:</p>
<p>Something changed.</p>
<p>before the user has to.</p>
<p>And that might be the simplest definition of good monitoring:</p>
<p>Knowing your software is unhealthy before your customers do.</p>
]]></content:encoded></item><item><title><![CDATA[AI Is Making Software Development Faster — But Production Is Becoming the Bottleneck]]></title><description><![CDATA[Has AI made you ship faster, or has it just moved the difficult part of your work somewhere else?]]></description><link>https://crescodb.hashnode.dev/ai-is-making-software-development-faster-but-production-is-becoming-the-bottleneck</link><guid isPermaLink="true">https://crescodb.hashnode.dev/ai-is-making-software-development-faster-but-production-is-becoming-the-bottleneck</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Fri, 02 Oct 2026 09:08:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abb01f3ad512977acd0dd66/aa0de12e-7fa1-458b-897c-550e47d11424.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A weird thing is happening in software development.</p>
<p>Building software has become ridiculously fast.</p>
<p>You can describe an application to an AI coding tool and have a working interface within minutes.</p>
<p>Need an API?</p>
<p>Generated.</p>
<p>Database schema?</p>
<p>Generated.</p>
<p>Authentication flow?</p>
<p>Probably generated too.</p>
<p>A solo developer can now build something in a weekend that might have taken a small team several weeks a few years ago.</p>
<p>That is genuinely impressive.</p>
<p>But there’s a point where the speed suddenly disappears.</p>
<p>It usually happens around the moment someone says:</p>
<p>“Alright. Put it in production.”</p>
<p>And now you have a completely different problem.</p>
<p>Generating code and running software are different jobs</p>
<p>AI is extremely good at helping us produce software.</p>
<p>It can generate:</p>
<p>routes database models API handlers authentication tests Dockerfiles CI configs</p>
<p>But generating those things doesn't automatically mean the resulting system is ready to serve real users.</p>
<p>Production introduces questions that don't matter nearly as much while you're working locally.</p>
<p>What happens if your server crashes?</p>
<p>What happens if a database migration fails halfway through?</p>
<p>What happens when two users modify the same data simultaneously?</p>
<p>Where are your logs?</p>
<p>How do you know the application is healthy?</p>
<p>Where are your backups?</p>
<p>Have you ever restored one?</p>
<p>How do you roll back a bad deployment?</p>
<p>What happens when your server runs out of memory?</p>
<p>Who gets alerted when any of this happens?</p>
<p>AI can help answer every one of those questions.</p>
<p>But somebody still has to ask them.</p>
<p>The bottleneck is moving</p>
<p>For years, one of the biggest constraints in software was simply writing the thing.</p>
<p>You needed enough engineering hours to turn an idea into functioning software.</p>
<p>AI is reducing that constraint.</p>
<p>Not eliminating it, but reducing it significantly.</p>
<p>So another constraint becomes more visible:</p>
<p>Operating what we build.</p>
<p>We are creating applications faster than ever.</p>
<p>But those applications still need databases, deployments, infrastructure, monitoring, migrations, security, backups and recovery.</p>
<p>If development becomes 5x faster while everything after development remains roughly the same, then production becomes a larger percentage of the work.</p>
<p>That changes the developer workflow.</p>
<p>“It works” is becoming an increasingly dangerous milestone</p>
<p>There's a huge difference between:</p>
<p>It works.</p>
<p>and:</p>
<p>It can survive production.</p>
<p>An application running on your laptop usually has ideal conditions.</p>
<p>You're probably the only user.</p>
<p>The database is nearby.</p>
<p>You know the environment.</p>
<p>You can restart everything.</p>
<p>You can read the terminal directly.</p>
<p>If something breaks, you are literally sitting in front of it.</p>
<p>Production removes most of those advantages.</p>
<p>Now the application needs to behave correctly when nobody is watching.</p>
<p>That means the definition of “finished” starts expanding.</p>
<p>Instead of:</p>
<p>Feature works ✓</p>
<p>you eventually need something closer to:</p>
<p>Feature works ✓</p>
<p>Tests pass ✓</p>
<p>Configuration verified ✓</p>
<p>Migration safe ✓</p>
<p>Deployment reproducible ✓</p>
<p>Monitoring active ✓</p>
<p>Backups available ✓</p>
<p>Rollback possible ✓</p>
<p>This part is much less exciting than generating a beautiful application from a prompt.</p>
<p>Unfortunately, users don't care which part was exciting.</p>
<p>They care that the software works.</p>
<p>AI can also create operational debt faster</p>
<p>There's another side to faster development.</p>
<p>When building becomes easier, it becomes easier to build more.</p>
<p>More features.</p>
<p>More endpoints.</p>
<p>More dependencies.</p>
<p>More services.</p>
<p>More database tables.</p>
<p>More infrastructure.</p>
<p>And every additional moving part becomes something that might eventually need to be operated.</p>
<p>You can create complexity faster than you can understand it.</p>
<p>That's an interesting problem.</p>
<p>A developer might previously spend three days implementing a feature and naturally become familiar with almost every part of it.</p>
<p>Now an AI agent might generate the same feature in twenty minutes.</p>
<p>The output arrived faster.</p>
<p>The developer's mental model did not necessarily accelerate at the same rate.</p>
<p>That gap matters when something breaks.</p>
<p>This changes what developers need to get good at</p>
<p>I don't think the lesson is that developers should stop using AI.</p>
<p>Quite the opposite.</p>
<p>The productivity increase is too useful to ignore.</p>
<p>But our skills probably need to move upward.</p>
<p>Knowing how to generate code becomes less valuable than knowing how to evaluate it.</p>
<p>Questions like:</p>
<p>Is this architecture appropriate? What assumptions does this code make? What happens when this fails? Is this secure? How will I observe this in production? How do I recover from failure? What happens at 100x the current traffic? What dependencies am I introducing?</p>
<p>Those questions become increasingly important.</p>
<p>The job starts moving from:</p>
<p>How do I write this?</p>
<p>toward:</p>
<p>How do I know this is actually good?</p>
<p>That is a much more interesting engineering problem.</p>
<p>Production is where abstractions meet reality</p>
<p>Development tools can hide an enormous amount of complexity.</p>
<p>And that's usually a good thing.</p>
<p>Good abstractions let us build faster.</p>
<p>But production eventually exposes whatever the abstraction didn't cover.</p>
<p>Your database doesn't care how quickly the application was generated when a migration locks a table.</p>
<p>Your server doesn't care that an AI wrote the endpoint when memory usage keeps growing.</p>
<p>Your users don't care how impressive the development process was when authentication goes down.</p>
<p>Reality remains annoyingly traditional.</p>
<p>Networks fail.</p>
<p>Disks fill.</p>
<p>Processes crash.</p>
<p>Humans make mistakes.</p>
<p>Dependencies break.</p>
<p>Credentials expire.</p>
<p>And software has to deal with all of it.</p>
<p>The next generation of developer tooling will probably move downstream</p>
<p>We've spent the last few years making creation dramatically easier.</p>
<p>AI coding agents.</p>
<p>App generators.</p>
<p>Better frameworks.</p>
<p>Managed databases.</p>
<p>Backend-as-a-service platforms.</p>
<p>Component libraries.</p>
<p>All of these reduce the time between idea and working software.</p>
<p>The next major opportunity may be making everything after creation equally simple.</p>
<p>Not just:</p>
<p>Build</p>
<p>but:</p>
<p>Verify → Deploy → Operate → Recover</p>
<p>Because if AI keeps making software creation faster, developers are going to produce more software.</p>
<p>And all of that software eventually has to run somewhere.</p>
<p>The interesting question isn't “Can AI build this?”</p>
<p>We're rapidly reaching a point where the answer to that question is often:</p>
<p>Probably.</p>
<p>The more useful questions are becoming:</p>
<p>Should we build it this way?</p>
<p>Is it ready for production?</p>
<p>Can we understand what happens when it fails?</p>
<p>Can we safely operate it?</p>
<p>Can we recover?</p>
<p>The impressive part of software development is becoming easier.</p>
<p>The boring part is becoming the bottleneck.</p>
<p>And ironically, the boring part is usually what determines whether the software actually survives.</p>
]]></content:encoded></item><item><title><![CDATA[Why Technical Writing Makes You a Better Software Developer]]></title><description><![CDATA[Most developers treat technical writing as something you do after you know what you're talking about. I've started thinking about it the other way around…]]></description><link>https://crescodb.hashnode.dev/why-technical-writing-makes-you-a-better-software-developer</link><guid isPermaLink="true">https://crescodb.hashnode.dev/why-technical-writing-makes-you-a-better-software-developer</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Wed, 30 Sep 2026 08:09:55 GMT</pubDate><content:encoded><![CDATA[<p>Most developers treat technical writing as something you do after you know what you're talking about.</p>
<p>I've started thinking about it the other way around.</p>
<p>Sometimes I don't realize how poorly I understand something until I try to explain it.</p>
<p>You can spend hours working with a piece of technology and feel completely comfortable with it.</p>
<p>Then somebody asks:</p>
<p>Why does it work that way?</p>
<p>And suddenly your brain goes:</p>
<p>Well… it just does.</p>
<p>That's usually the point where you discover the difference between recognizing something and actually understanding it.</p>
<p>Code lets you hide behind working software</p>
<p>Programming has an interesting loophole.</p>
<p>Your code can work even when your mental model is incomplete.</p>
<p>You can know that:</p>
<p>await db.query(...)</p>
<p>works.</p>
<p>You can know where to put it.</p>
<p>You can know how to handle the response.</p>
<p>But that doesn't necessarily mean you understand:</p>
<p>what happens when the connection drops how the connection pool behaves when the promise resolves what happens under concurrent requests how the database handles the query what failure actually looks like</p>
<p>And that's fine.</p>
<p>Nobody needs to understand every abstraction underneath every line of code they write.</p>
<p>But the gaps become obvious when you attempt to explain the system to another person.</p>
<p>Writing removes the autocomplete from your understanding.</p>
<p>Explaining something forces you to find the missing pieces</p>
<p>Let's say I want to write:</p>
<p>"A database connection pool improves performance."</p>
<p>Easy sentence.</p>
<p>Then I ask myself:</p>
<p>Why?</p>
<p>Because we're reusing connections instead of creating one for every request.</p>
<p>Okay.</p>
<p>Why is opening a new database connection expensive?</p>
<p>Now we're talking about networking, authentication, resource allocation and connection setup.</p>
<p>What happens if the pool is exhausted?</p>
<p>What decides how many connections should exist?</p>
<p>Can too many connections make performance worse instead of better?</p>
<p>Five minutes ago I thought I was writing one paragraph.</p>
<p>Now I'm reading PostgreSQL documentation.</p>
<p>That's what I like about writing.</p>
<p>It exposes the edges of what you actually know.</p>
<p>Tutorials are especially unforgiving</p>
<p>When you're coding alone, you can skip steps mentally.</p>
<p>You know that before this:</p>
<p>npm run dev</p>
<p>you already:</p>
<p>npm install</p>
<p>created .env, configured the database and probably fixed three errors you no longer remember.</p>
<p>The reader doesn't know any of that.</p>
<p>So when you're writing a tutorial, you have to reconstruct the real sequence.</p>
<p>That forces you to think more carefully about:</p>
<p>dependencies assumptions defaults edge cases setup failure states</p>
<p>You begin noticing complexity you've learned to ignore.</p>
<p>I've had several situations where writing about a workflow made me think:</p>
<p>Why does this require so many steps in the first place?</p>
<p>Which is also where product ideas tend to appear.</p>
<p>Writing is surprisingly good product research</p>
<p>This one wasn't obvious to me initially.</p>
<p>When you try to explain a technical problem properly, you naturally start researching how other developers solve it.</p>
<p>Suppose I'm writing about deploying a backend.</p>
<p>I might start with:</p>
<p>"Deploying a backend requires…"</p>
<p>Then I need examples.</p>
<p>So I start looking at:</p>
<p>Docker Railway Render VPS deployments CI/CD migrations reverse proxies environment management health checks</p>
<p>And while reading, patterns start showing up.</p>
<p>Everyone has slightly different solutions to the same underlying problems.</p>
<p>You start noticing where tools overlap.</p>
<p>Where workflows are unnecessarily complicated.</p>
<p>Where developers complain.</p>
<p>Where documentation repeatedly has to explain the same thing.</p>
<p>You weren't doing market research.</p>
<p>You were trying to write a decent article.</p>
<p>But you end up learning a lot about the market anyway.</p>
<p>Writing also generates ideas</p>
<p>One article tends to create five more.</p>
<p>You start writing about logging.</p>
<p>Then you realize error reporting deserves its own article.</p>
<p>That leads to observability.</p>
<p>Then production debugging.</p>
<p>Then distributed tracing.</p>
<p>Then suddenly you have a backlog of topics you didn't have yesterday.</p>
<p>It's basically recursion for ideas.</p>
<p>The most useful part is that the ideas aren't coming from:</p>
<p>"What should I post today?"</p>
<p>They're coming from questions you naturally encountered while trying to understand something.</p>
<p>Those tend to make better articles.</p>
<p>It changes how you read documentation</p>
<p>Before I started writing more often, documentation was usually transactional.</p>
<p>I had a problem.</p>
<p>I searched for the answer.</p>
<p>I copied the relevant mental model into my brain.</p>
<p>Then I left.</p>
<p>Now I notice more things.</p>
<p>Why did the documentation explain this concept before that one?</p>
<p>What misconception are they trying to prevent?</p>
<p>Why is this warning here?</p>
<p>What edge case was common enough that somebody decided it deserved an entire section?</p>
<p>Good technical documentation isn't only teaching you the technology.</p>
<p>It's showing you where people usually misunderstand it.</p>
<p>That information is incredibly useful.</p>
<p>It makes vague opinions harder to keep</p>
<p>Developers have plenty of opinions.</p>
<p>"This framework is slow."</p>
<p>"Microservices are overkill."</p>
<p>"SQL is better."</p>
<p>"Serverless is expensive."</p>
<p>"X is easier than Y."</p>
<p>Those opinions are easy to say.</p>
<p>They're much harder to publish when you know another developer can ask:</p>
<p>Based on what?</p>
<p>Writing forces you to qualify things.</p>
<p>Instead of:</p>
<p>Serverless is expensive.</p>
<p>You start writing:</p>
<p>Serverless can become expensive for workloads with these characteristics…</p>
<p>Which is a much better statement.</p>
<p>You stop trying to sound certain and start trying to be accurate.</p>
<p>That's useful far beyond writing.</p>
<p>You don't have to become a "content creator"</p>
<p>This is probably the part developers hate.</p>
<p>The moment somebody says:</p>
<p>You should write.</p>
<p>it starts sounding like:</p>
<p>Build your personal brand. Post three times every day. Optimize engagement.</p>
<p>I'm not talking about that.</p>
<p>You can write once a month.</p>
<p>You can have 20 readers.</p>
<p>You can publish something that gets absolutely no traction.</p>
<p>The value isn't only distribution.</p>
<p>The process itself is useful.</p>
<p>You are forcing your brain to turn:</p>
<p>I think I understand this</p>
<p>into:</p>
<p>Here is exactly how I think this works.</p>
<p>Those are very different things.</p>
<p>The simplest writing habit I've found</p>
<p>When something makes me stop while coding, I write it down.</p>
<p>That's it.</p>
<p>Things like:</p>
<p>Why did this migration fail?</p>
<p>Why does this API behave differently in production?</p>
<p>Why are these two tools solving the same problem differently?</p>
<p>Why does this configuration exist?</p>
<p>What actually happens when this command runs?</p>
<p>You don't need the article immediately.</p>
<p>Just keep the questions.</p>
<p>When you eventually sit down to write, you're not staring at a blank editor trying to invent a topic.</p>
<p>You're answering something that genuinely made you curious.</p>
<p>And curiosity is usually a better starting point than a content calendar.</p>
<p>Writing is part of learning</p>
<p>I used to think the sequence was:</p>
<p>Learn → understand → write</p>
<p>Now I think it's closer to:</p>
<p>Learn → write → discover what you don't understand → learn more → rewrite</p>
<p>And the loop keeps going.</p>
<p>The article is almost a side effect.</p>
<p>The real output is a clearer mental model.</p>
<p>So if you're learning something, building something or repeatedly solving the same technical problem, try writing about it.</p>
<p>Even if nobody reads it.</p>
<p>You'll probably discover something you didn't know you didn't know.</p>
]]></content:encoded></item><item><title><![CDATA[Backend Production Readiness Checklist: What to Verify Before You Deploy]]></title><description><![CDATA[Your Backend Works Locally. That Doesn’t Mean It’s Production-Ready #backend #webdev #node #devops]]></description><link>https://crescodb.hashnode.dev/backend-production-readiness-checklist-what-to-verify-before-you-deploy</link><guid isPermaLink="true">https://crescodb.hashnode.dev/backend-production-readiness-checklist-what-to-verify-before-you-deploy</guid><dc:creator><![CDATA[The Saint]]></dc:creator><pubDate>Tue, 29 Sep 2026 00:17:17 GMT</pubDate><content:encoded><![CDATA[<p>You finish the feature.</p>
<p>The API responds correctly.</p>
<p>The database connection works.</p>
<p>Authentication passes.</p>
<p>You run the app locally for the tenth time and everything looks fine.</p>
<p>Then someone says:</p>
<p>“Cool. Let’s deploy it.”</p>
<p>And suddenly the problem changes completely.</p>
<p>A backend that works on your laptop is not necessarily a backend that is ready for production.</p>
<p>Local development proves that your application can run in your environment.</p>
<p>Production asks a much harder question:</p>
<p>Can this thing keep working when the environment is no longer under your control?</p>
<p>That difference is where a lot of small teams get caught.</p>
<p>Local development hides a lot</p>
<p>Your development machine is unusually forgiving.</p>
<p>You probably have:</p>
<p>the correct environment variables direct access to the database the right runtime installed every dependency available full filesystem permissions a predictable network logs directly in your terminal one developer making requests a database you can reset without consequences</p>
<p>Production gives you none of those guarantees.</p>
<p>The application now needs to survive restarts, failed migrations, missing secrets, expired credentials, network interruptions, disk limits, bad deployments and actual users.</p>
<p>That means “it runs locally” is only the beginning.</p>
<ol>
<li>Configuration becomes infrastructure</li>
</ol>
<p>Locally, this might be enough:</p>
<p>DATABASE_URL=postgresql://localhost:5432/app JWT_SECRET=dev-secret PORT=3000</p>
<p>In production, configuration has consequences.</p>
<p>What happens if JWT_SECRET is missing?</p>
<p>Does the application refuse to start?</p>
<p>Or does it silently fall back to something unsafe?</p>
<p>What happens when:</p>
<p>process.env.DATABASE_URL</p>
<p>is undefined?</p>
<p>A production backend should fail predictably.</p>
<p>For example:</p>
<p>const required = [ "DATABASE_URL", "JWT_SECRET" ];</p>
<p>for (const variable of required) { if (!process.env[variable]) { throw new Error( <code>Missing required environment variable: ${variable}</code> ); } }</p>
<p>It is simple, but it is much better than discovering the problem after deployment.</p>
<p>Configuration should be validated before the application begins accepting traffic.</p>
<ol>
<li>Database migrations stop being harmless</li>
</ol>
<p>During development, database changes are easy.</p>
<p>Something breaks?</p>
<p>Reset the database.</p>
<p>Change the schema.</p>
<p>Run the migration again.</p>
<p>Move on.</p>
<p>Production is different because the database contains data you cannot casually destroy.</p>
<p>Imagine changing:</p>
<p>ALTER TABLE users DROP COLUMN username;</p>
<p>locally.</p>
<p>No big deal.</p>
<p>Now imagine doing that with 50,000 production users and discovering another part of the application still depends on username.</p>
<p>The migration technically succeeded.</p>
<p>The deployment still failed.</p>
<p>Production migrations need to consider:</p>
<p>backwards compatibility existing data rollback strategy application version compatibility long-running operations database locks</p>
<p>For risky changes, the safer pattern is often incremental.</p>
<p>Instead of:</p>
<p>remove old field deploy new code hope</p>
<p>you might:</p>
<p>add new field deploy compatible code migrate data verify remove old field later</p>
<p>The boring version is usually the safer version.</p>
<ol>
<li>“The server is running” is not a health check</li>
</ol>
<p>Your process can be alive while your application is effectively dead.</p>
<p>Maybe:</p>
<p>PostgreSQL is unreachable Redis is unavailable migrations failed storage credentials are invalid an external dependency is timing out</p>
<p>Your process manager may still happily report:</p>
<p>RUNNING</p>
<p>That is why production systems usually need an actual health endpoint.</p>
<p>A simple example:</p>
<p>app.get("/health", async (req, res) =&gt; { try { await db.query("SELECT 1");</p>
<pre><code class="language-plaintext">res.status(200).json({
  status: "healthy"
});
</code></pre>
<p>} catch { res.status(503).json({ status: "unhealthy" }); } });</p>
<p>Now your infrastructure has something meaningful to test.</p>
<p>Not:</p>
<p>Is Node.js still running?</p>
<p>But:</p>
<p>Can this application actually serve requests?</p>
<p>That distinction matters.</p>
<ol>
<li>Logging changes when nobody is watching the terminal</li>
</ol>
<p>During development:</p>
<p>console.log(error);</p>
<p>often feels perfectly adequate.</p>
<p>You are already looking at the terminal.</p>
<p>Production errors usually happen while nobody is looking.</p>
<p>And this:</p>
<p>Something went wrong</p>
<p>is almost useless three hours later.</p>
<p>Useful production logs should tell you things like:</p>
<p>what happened when it happened which request triggered it which service failed how severe the failure was</p>
<p>Structured logs are much easier to search and process.</p>
<p>Instead of:</p>
<p>console.log("Database error");</p>
<p>something closer to:</p>
<p>logger.error({ event: "database_connection_failed", requestId, error: error.message });</p>
<p>gives you something you can actually investigate.</p>
<p>Production observability is not about printing more text.</p>
<p>It is about leaving enough evidence to understand failures after they happen.</p>
<ol>
<li>Backups do not matter until restoration works</li>
</ol>
<p>A lot of systems technically have backups.</p>
<p>Far fewer know whether those backups can actually be restored.</p>
<p>There is an uncomfortable difference between:</p>
<p>“We create a backup every night.”</p>
<p>and:</p>
<p>“We successfully restored yesterday’s backup into a clean database.”</p>
<p>The second statement proves something.</p>
<p>The first only proves that a file exists somewhere.</p>
<p>A production backup strategy should answer:</p>
<p>How often do backups run? Where are they stored? How long are they retained? Are they encrypted? How quickly can they be restored? When was restoration last tested?</p>
<p>A backup you have never restored is still partly a hypothesis.</p>
<ol>
<li>Deployment without rollback is a one-way door</li>
</ol>
<p>Imagine deploying version 1.8.</p>
<p>Ten minutes later:</p>
<p>error rates spike users cannot authenticate database queries slow down one endpoint starts returning 500</p>
<p>What happens next?</p>
<p>If the answer is:</p>
<p>“Give me twenty minutes while I fix it.”</p>
<p>you do not really have a recovery strategy.</p>
<p>Sometimes the fastest fix is simply returning to the last known-good version.</p>
<p>That requires your deployment system to know:</p>
<p>v1.6 v1.7 v1.8 ← current</p>
<p>and to make returning to:</p>
<p>v1.7</p>
<p>boring.</p>
<p>Production reliability is not about never breaking anything.</p>
<p>Eventually something will break.</p>
<p>Reliability is largely about reducing how expensive failure becomes.</p>
<ol>
<li>Production traffic exposes assumptions</li>
</ol>
<p>During local development you might be the only user.</p>
<p>Production introduces:</p>
<p>User 1 User 2 User 3 ... User 10,000</p>
<p>And all of them can do things at inconvenient times.</p>
<p>You start encountering problems like:</p>
<p>race conditions exhausted database connections duplicate requests slow queries rate abuse concurrent writes memory growth unexpectedly large payloads</p>
<p>For example, this query may seem harmless:</p>
<p>SELECT * FROM orders;</p>
<p>until orders contains several million records.</p>
<p>Production has a habit of turning assumptions into benchmarks.</p>
<p>A better definition of “done”</p>
<p>For a backend feature, “done” should probably not mean:</p>
<p>✓ Works locally</p>
<p>A stronger checklist is closer to:</p>
<p>✓ Works locally ✓ Configuration validated ✓ Database changes are safe ✓ Authentication and permissions verified ✓ Health checks available ✓ Errors observable ✓ Deployment reproducible ✓ Backups exist ✓ Restoration tested ✓ Rollback possible ✓ Production behavior monitored</p>
<p>Not every side project needs enterprise infrastructure.</p>
<p>But every application should know what level of failure it is prepared to tolerate.</p>
<p>That is the important part.</p>
<p>The backend lifecycle is bigger than building</p>
<p>Developers spend a lot of time discussing how quickly we can create APIs, databases and authentication.</p>
<p>Those things matter.</p>
<p>But building the backend is only one stage.</p>
<p>Eventually the lifecycle becomes:</p>
<p>Build → Verify → Deploy → Operate → Own</p>
<p>And the transitions between those stages are where a surprising amount of engineering time disappears.</p>
<p>That is also the problem we have been thinking about while building CrescoDB: how to give smaller engineering teams a cleaner path through the backend lifecycle without forcing them to stitch together an increasingly complicated production stack.</p>
<p>But regardless of which tools you use, the principle stays the same:</p>
<p>A backend isn't production-ready because it runs. It's production-ready when you understand how it fails.</p>
]]></content:encoded></item></channel></rss>