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:
| Model | You manage | Provider manages | Typical failure you own |
|---|---|---|---|
| Bare VPS (DIY) | OS, patches, Docker, Nginx, TLS, backups, DB, monitoring | Hardware, hypervisor | Everything from disk-full to expired certs |
| IaaS (VMs + managed pieces) | App runtime, networking, IAM, glue between services | Hardware, some managed services | Misconfigured VPC or IAM, cost sprawl |
| Platform as a service | App code, env vars, schema | Runtime, TLS, routing, scaling, managed DB and backups | Bugs in your own code |
| Serverless functions | Function code, cold-start tuning | Everything, but with runtime limits | Execution 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, adocker system pruneschedule and alerts, the disk fills. Postgres can't write, so it stops. - Backups that you've never restored. A nightly
pg_dumpcron 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/nullIf 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.dumpDo 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:
- Day 1: Add
PORThandling, a/healthzendpoint, and move all config into env vars. - Day 2: Verify the image runs locally with production-like env vars and no mounted volumes.
- Day 3: Create the app, database and bucket on the new platform. Deploy to the platform URL.
- Day 4: Rehearse the database dump/restore and the file sync. Test sign-up, login and uploads end to end.
- Day 5: Lower DNS TTL. Test a backup restore.
- Day 6: Short maintenance window, final data sync, DNS switch.
- 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.