Ditch brittle DIY: a stable platform as a service for $9/mo

By Mark Hayes
Ditch brittle DIY: a stable platform as a service for $9/mo

There's a particular kind of tired that only solo founders know. It's 2 AM, your first beta users are in Slack asking why the app is down, and you're SSH'd into a $12 droplet trying to figure out why Postgres refuses to start. (The answer is almost always the same: the disk is full, because logs have been growing for three months and nobody set up rotation.) Six hours later the app is back, you've shipped zero features, and you've learned that "cheap" hosting has a price that never shows up on the invoice.

Or maybe your pain went the other way. You moved to a slick usage-billed platform to avoid exactly this, and then a $180 bill landed because a memory leak quietly scaled your container up for two weeks.

Neither of these is a skill problem. They're what happens when a single developer tries to be the app team, the database administrator and the on-call SRE at once. This post explains what a platform as a service actually does for you, how to judge whether a given provider will fix your problems or just move them, and how to get a Dockerized MVP plus a managed Postgres into production on a budget you can state out loud before you deploy.

What platform as a service actually means

Platform as a service (PaaS) is the cloud model where the provider runs everything below your application: hardware, operating system, container runtime, networking, TLS, load balancing, and usually databases and storage as managed add-ons. You bring the code (or a container image) and configuration. They bring the machine that keeps it running.

It helps to see where PaaS sits against the other options:

ModelYou manageProvider managesTypical failure you own
Bare VPS (DIY)OS, patches, Docker, Nginx, TLS, backups, DB, monitoringHardware, hypervisorEverything from disk-full to expired certs
IaaS (VMs + managed pieces)App runtime, networking, IAM, glue between servicesHardware, some managed servicesMisconfigured VPC or IAM, cost sprawl
Platform as a serviceApp code, env vars, schemaRuntime, TLS, routing, scaling, managed DB and backupsBugs in your own code
Serverless functionsFunction code, cold-start tuningEverything, but with runtime limitsExecution limits, vendor-specific rewrites

The line that matters for a solo founder is the last column. On a DIY box, every row of the operations manual is yours. On a PaaS, the failures you own shrink to the ones you're actually qualified to fix: your own code.

A platform as a service isn't about avoiding servers. It's about deciding which failures you're willing to be woken up for.

The brittle DIY tax, itemized

DIY hosting looks cheap because the invoice only lists the VM. The real bill is paid in hours and outages. Here's what a "simple" single-droplet setup quietly asks you to maintain:

  • Disk space. Docker images, build cache, container logs and Postgres WAL all grow. Without logrotate, a docker system prune schedule and alerts, the disk fills. Postgres can't write, so it stops.
  • Backups that you've never restored. A nightly pg_dump cron job that writes to the same disk as the database isn't a backup. It's a second copy of the thing that's about to be lost.
  • TLS renewal. Certbot usually works until an Nginx config change breaks the renewal hook, and you find out 90 days later.
  • OS patches and reboots. Unattended upgrades can restart services you didn't expect. Skipping them leaves known CVEs open.
  • A single point of failure. One VM means one kernel panic takes down the app, the database and the file uploads together.
  • Monitoring. If you don't have uptime checks and disk alerts, your users are your monitoring.

If you want to see how close you are to the disk-full outage right now, run this on your server:

df -h /
docker system df
sudo du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -5
sudo du -sh /var/lib/postgresql 2>/dev/null

If any of those numbers surprise you, that's the tax. Every hour spent on this is an hour not spent on the thing your pre-seed investors will actually look at.

Why "just use a big cloud" isn't the fix either

The usual reaction to a DIY outage is to go to a hyperscaler and do it "properly." For a team of one, this tends to trade one kind of fragility for another. You now own VPC design, IAM policies, security groups, a load balancer, a managed database with its own networking rules, an object storage bucket policy, and a billing console with dozens of line items. None of that is wrong. It's just a full-time job, and you already have one.

Usage-billed developer platforms are the other common move. They're genuinely pleasant to deploy on. The catch is that the bill tracks resource consumption, and at MVP stage your resource consumption is the least predictable number in your business. A leaky background job, a retry loop, or a crawler hitting an expensive endpoint can all turn into a bill spike. You can manage this with alerts and hard limits, but you have to know to set them.

The fix isn't "more cloud." It's a platform that absorbs operations work and gives you a number you can plan around.

How to judge platform as a service providers

Plenty of platform as a service providers will promise simplicity. Use these questions to test the promise before you migrate.

Can you state the monthly cost before you deploy?

Write down what you think your first month will cost. If you need a calculator, a free trial and a spreadsheet to answer, you'll have the same anxiety on the new platform. Look for a flat plan price, a clear statement of what's included, and a documented behavior when you exceed it.

Does it run your existing Docker image unchanged?

If your app already runs in docker compose up, the platform should run that same image. Anything that requires rewriting your API into functions or adopting a proprietary build format adds weeks and new failure modes.

Is the database actually managed?

"Managed" should mean automated backups, restore without a support ticket, storage that doesn't silently hit a wall, and version upgrades you don't have to script. Ask how you restore a backup, and try it once before launch.

How many vendors does your stack need?

Count them: app host, database, object storage, CDN, maybe a separate cron or worker host. Each vendor is a separate bill, dashboard, credential set and outage page. Fewer is better when you're the only person on call.

Can a non-engineer co-founder use the dashboard?

Your co-founder should be able to see whether the app is up, roll back a bad deploy, or check the last build without calling you. If the dashboard is a wall of YAML, that won't happen.

What happens when things go wrong?

Look for health checks, automatic restarts, deploy history with rollback, and logs you can read in the browser.

Prepare your app for any PaaS

Whatever provider you pick, a few small changes make a Dockerized app portable and PaaS-friendly. They take an afternoon, and they're worth doing even if you stay where you are.

1. Read the port and config from the environment

Platforms route traffic to a port they tell you about via an environment variable. Hardcoding 3000 is one of the most common first-deploy failures.

// server.js
const express = require("express");
const app = express();

const port = process.env.PORT || 3000;

app.get("/healthz", async (req, res) => {
  try {
    await db.query("SELECT 1"); // your DB client
    res.status(200).json({ ok: true });
  } catch (err) {
    res.status(503).json({ ok: false });
  }
});

app.listen(port, "0.0.0.0", () => console.log(`listening on ${port}`));

Note the 0.0.0.0 bind. Listening on localhost inside a container means nothing outside the container can reach you.

2. Add a health endpoint

The /healthz route above lets the platform know when a new release is actually ready and when a running one has died. Keep it cheap. A SELECT 1 is fine. A full dependency audit is not.

3. Keep the container stateless

Anything written to the container filesystem disappears on the next deploy or restart. User uploads go to object storage, sessions go to the database or a cache, and logs go to stdout.

4. Use a lean, production Dockerfile

# syntax=docker/dockerfile:1
FROM node:22-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "server.js"]

Smaller images build and deploy faster. Running as a non-root user is a cheap security win.

5. Put every secret in environment variables

DATABASE_URL, S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, SESSION_SECRET. If a value differs between your laptop and production, it belongs in the environment, not the code.

Moving the stack onto Granite.so

Granite.so is a platform as a service built for exactly this setup: a Dockerized app, a Postgres database and a pile of user files, run by someone who doesn't want to be a sysadmin. (If you've followed us for a while, you knew us as NoVPS. We renamed in June 2026; the product is the same.) Here's the migration at a high level.

1. Connect your Git repository or point at a Docker image. Granite.so builds from your Dockerfile and redeploys automatically on every push to the branch you choose. If you already publish images to a registry, you can deploy the image directly instead.

2. Create a managed Postgres database. Backups are automatic, so the "cron job writing to the same disk" pattern goes away.

3. Copy your data over. From your old server, dump and restore:

pg_dump --format=custom --no-owner --no-acl "$OLD_DATABASE_URL" > app.dump
pg_restore --no-owner --no-acl --dbname "$NEW_DATABASE_URL" app.dump

Do this once as a rehearsal, check row counts on your largest tables, then do it again during a short maintenance window before you switch DNS.

4. Create an S3-compatible storage bucket. It comes with a CDN, so user uploads and static assets are served from the edge without a separate CDN vendor. Move existing files with any S3 tool, for example:

aws s3 sync ./uploads "s3://$S3_BUCKET/uploads" --endpoint-url "$S3_ENDPOINT"

5. Set environment variables in the dashboard: the database URL, the storage credentials and your app secrets.

6. Deploy, test against the platform URL, then point your domain. Lower your DNS TTL a day in advance so the cutover is fast and reversible.

Scaling is automatic, so a traffic spike from a launch post doesn't require you to resize a VM at midnight.

Before launch, restore a backup into a scratch database and run your app against it. A backup you've never restored is a hope, not a plan.

The $80 budget, done honestly

Most founders at this stage have a hard ceiling somewhere around $80 a month for infrastructure. Here's how the numbers compare in practice.

Typical DIY-plus-vendors stack:

  • VPS for the app
  • A second VPS or a managed database for Postgres (or the database on the same box, and the risk that comes with it)
  • Object storage from one vendor, CDN from another
  • An uptime monitor
  • Your time: realistically several hours a week at this stage, plus the occasional lost night

The dollar total might fit under $80. The time total usually doesn't fit anywhere.

Consolidated on a platform as a service:

Granite.so's Hobby plan is $9/month and includes $15 of usage credits each month, which covers an MVP-sized app, database and storage for your first beta users. You know the floor before you deploy, the credits absorb normal usage, and there's one bill to explain to investors. Check the current pricing page for the exact resource rates and higher tiers once you outgrow Hobby.

That leaves most of your $80 for things that actually move the business: email delivery, error tracking, analytics.

When a PaaS is the wrong call

To be fair to the DIY crowd, a platform as a service isn't always the answer:

  • You need kernel-level control (custom networking, GPU drivers, unusual system packages that can't live in a container).
  • You're running sustained heavy compute where raw VM pricing is dramatically cheaper and you have the ops skills to run it.
  • Compliance requires a specific region, provider or certification that a given PaaS doesn't offer.
  • You enjoy running servers and it genuinely costs you nothing in focus. Some people do, and that's fine.

For most pre-seed SaaS MVPs (a web app, an API, a Postgres database, and file uploads), none of these apply.

A one-week migration plan

If you want to move before your first 10 users arrive, here's a realistic schedule that fits around product work:

  1. Day 1: Add PORT handling, a /healthz endpoint, and move all config into env vars.
  2. Day 2: Verify the image runs locally with production-like env vars and no mounted volumes.
  3. Day 3: Create the app, database and bucket on the new platform. Deploy to the platform URL.
  4. Day 4: Rehearse the database dump/restore and the file sync. Test sign-up, login and uploads end to end.
  5. Day 5: Lower DNS TTL. Test a backup restore.
  6. Day 6: Short maintenance window, final data sync, DNS switch.
  7. Day 7: Watch logs, then shut down the old server after a few quiet days. Keep a final dump somewhere safe.

Stop paying the DIY tax

A droplet you have to babysit isn't cheaper than a platform as a service. It just bills you in sleep instead of dollars. Your job for the next six weeks is to get ten people paying for your product, not to become an expert in log rotation.

If you want a stable home for your Docker app, managed Postgres and storage with a price you can say out loud before deploying, try Granite.so on the Hobby plan: connect your repo, attach a database, and ship.

Keep reading

More posts tagged “Community”.
Stop S3 storage bill shock: migration to low-cost storage

Your S3 storage bill is rarely about gigabytes stored. Here is where the money actually goes, and a step-by-step plan to move your files somewhere predictable.

Fri, Oct 16, 2026
Scale 12 factor apps: 12-factor app principles on Granite.so

Your Docker container works on localhost and beta users are waiting. Here is how to apply the 12 factors in one afternoon, without Terraform.

Wed, Oct 7, 2026
Railway Pricing 2026: What Web + Worker + Postgres Costs

Railway bills the memory your services hold, by the second. Worked costs for a web app, a worker and Postgres on Hobby and Pro — and when usage-based pricing stops being cheap.

Tue, Oct 6, 2026
Railway DB vs Granite.so: migrate your database for predictable costs

Your Railway DB bill jumped and you can't explain why. Here is an honest comparison and a step-by-step migration of app and database to Granite.so in one afternoon.

Wed, Sep 30, 2026
GHCR vs Granite: Docker registry or managed deploys?

GHCR is a great place to store your Docker images — but it doesn't run them. This guide breaks down what GitHub Container Registry actually does, where teams hit the wall trying to deploy on top of it, and how to decide between rolling your own infrastructure, pairing GHCR with a managed platform, or skipping the registry question entirely. Includes a concrete GitHub Actions walkthrough and an honest take on the trade-offs.

Mon, May 11, 2026
Docker vs Kubernetes: when Docker Compose is enough

A solo-founder decision framework for choosing between Docker Compose, Kubernetes, and managed platforms. Includes a five-step decision tree, signals that you've outgrown Compose, and common mistakes early-stage teams make.

Mon, May 4, 2026
Be first in line for updates
and special pricing
Get early access to new features and exclusive discounts delivered straight to your inbox