DEV Community

Cover image for Ship It With a Boring Stack: The 2026 Startup Default That Actually Works
Gabriel Anhaia
Gabriel Anhaia

Posted on

Ship It With a Boring Stack: The 2026 Startup Default That Actually Works


You have four users and a Kubernetes cluster.

You set it up because the conference talk made it sound inevitable. There are three microservices, a service mesh, a message broker you read about on Hacker News, and a managed search cluster that costs more per month than your revenue. Deploys take twenty minutes when they work. You spend your weekends reading runbooks you wrote for an outage volume you do not have.

None of that is shipping product. All of it is shipping a resume.

The stack that gets you to a real business in the first eighteen months is boring. Postgres. One monolith. A queue. A single cloud account. That is the whole kit, and it scales further than the architecture you keep reaching for.

Why boring scales further

Boring tools have a property the shiny ones do not: a decade of people hitting the same wall you are about to hit, then writing down how they got past it. When your Postgres query is slow at 2 a.m., the answer is the first result on every search engine. When your bespoke event-sourcing layer is slow, the answer is in your own head, and your head is asleep.

Every layer you add is a layer that can page you. Microservices turn a function call into a network call, and network calls fail in ways function calls never do: timeouts, retries, partial failures, version skew between two services you deployed an hour apart. You inherit all of that distributed-systems pain to solve a scaling problem you will not have until you have a hundred times the users you have today.

The boring stack trades theoretical scale for something you need more: the ability to change your mind cheaply. You are going to be wrong about your product. A monolith lets you be wrong and recover in an afternoon. A fleet of services makes every wrong guess a migration.

Postgres is the whole data tier

Reach for Postgres first, second, and third. It is a relational store, a JSON document store via jsonb, a full-text search engine, a geospatial database, and a job queue, all in one process you already run.

That last one surprises people. You do not need a separate broker for background work on day one. Postgres can hand a row to exactly one worker with FOR UPDATE SKIP LOCKED:

-- Claim one job, skip rows another
-- worker already holds.
UPDATE jobs
SET status = 'running',
    picked_at = now()
WHERE id = (
  SELECT id FROM jobs
  WHERE status = 'queued'
  ORDER BY created_at
  FOR UPDATE SKIP LOCKED
  LIMIT 1
)
RETURNING id, payload;
Enter fullscreen mode Exit fullscreen mode

That query is your queue. No broker to operate, no second system to back up, no new failure mode. When throughput outgrows it, you swap in a dedicated queue, and the swap is one module, not a rewrite, because the rest of your app never knew the difference.

Store the files somewhere else, though. User uploads go to object storage with an S3-compatible API, not your database and not the server's local disk, which vanishes the day you rebuild the box.

# Pre-signed upload URL: the browser
# uploads straight to storage, your
# server never touches the bytes.
url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "uploads",
        "Key": f"users/{user_id}/{filename}",
    },
    ExpiresIn=900,
)
Enter fullscreen mode Exit fullscreen mode

The monolith is a feature, not debt

One deployable unit. One repository. One process you can run on your laptop with a single command. That is the shape that lets a small team move fast, because nobody has to coordinate across service boundaries to ship a feature that touches two parts of the product.

A monolith is not a mess. You still draw lines inside it: a module for billing, a module for accounts, a module for notifications, each with its own boundary. The difference is the lines are function signatures, not HTTP contracts. You get the organization without the operational tax.

// Boundaries live in code, not in YAML.
// billing/ never imports notifications/
// internals — only its public funcs.
package billing

type Service struct {
    db    *pgxpool.Pool
    queue Enqueuer
}

func (s *Service) Charge(
    ctx context.Context,
    accountID string,
    cents int64,
) error {
    // ... charge logic ...
    return s.queue.Enqueue(ctx, "receipt.send", accountID)
}
Enter fullscreen mode Exit fullscreen mode

When one of those modules genuinely needs to scale on its own hardware, you lift it out. But you do that with a working business and real numbers telling you which module, not on a guess made before you had a single customer. The monolith that ran your first eighteen months gave you those numbers for free.

One cloud, one account, one bill

Pick one cloud and stay there. The multi-cloud pitch is for companies negotiating eight-figure contracts, not for you. Spreading across providers to "avoid lock-in" buys you two of every problem and an integration layer that is itself the worst lock-in you will ever own.

Keep the footprint small inside that one cloud, too. A virtual machine or a single managed container service, managed Postgres, object storage, and a CDN in front. That is the entire bill, and you can read it on one screen. Hetzner, DigitalOcean, Fly, a small AWS footprint, any of them work. What matters is that there is one place to look when something breaks and one invoice that tells you what you are spending.

Resist the managed-service buffet. You do not need a data warehouse, a stream processor, a feature store, and a service mesh. You need to know whether anyone is using the thing you built. A journalctl tail and an uptime ping cover your first months better than a six-service observability platform you will spend a week wiring up.

The first-18-months kit

Here is the whole thing on one page. Match the layer to what you actually have, not to what you hope to have.

Layer Pick Why
Compute One VM or one container service One thing to deploy, one thing to debug
App A monolith with internal modules Change your mind in an afternoon
Data Managed Postgres Relational, JSON, search, geo in one
Background work Postgres queue (SKIP LOCKED) No broker until throughput demands it
Files S3-compatible object storage Survives a server rebuild
Edge A CDN with free TLS HTTPS and caching without cron jobs
Deploys Push to main, tests gate it No SSH-and-pray
Eyes on it Uptime ping plus error tracking Know it broke before users tell you

Nothing on that list is interesting. That is the point. Every row is a tool with a long track record, a large community, and an obvious upgrade path the day you outgrow it. You will outgrow some of these. You will outgrow them with paying customers, real load numbers, and the calm of changing one piece at a time.

The career math nobody tells you

The resume-driven stack feels like the safe career move. It is the opposite. The engineer who shipped a working product on Postgres and a monolith has a story about a business that survived. The engineer who ran a beautiful Kubernetes cluster for an app that never found users has a story about infrastructure.

Hiring managers worth working for can tell the difference. Shipping is the skill that compounds. The exotic tooling will still be there when you have the scale that calls for it, and you will reach for it with judgment instead of fear of looking unsophisticated.

Boring is not a compromise you make until you can afford better. Boring is the better. The teams that look like they got lucky with their architecture usually just refused to add anything they could not yet justify.

Pick the kit above. Ship the thing. Add complexity the day a real number demands it, and not one day sooner.


If this lined up with how you think about shipping, the same reasoning runs through every layer of the stack in Ship It — what to pick, when to add the next piece, and what to skip until a paying customer makes you. It is the long version of this post, organized the same way: match the tool to the stage you are actually in.

Ship It — the pragmatic startup tech stack

Top comments (0)