Skip to content
← Journal
6 min readAta Mohammadi

One Postgres container, not nine

Every application on our infrastructure shares a single Postgres instance. Here is why per-app database containers are a false economy, and the protocol that makes sharing safe.

The default move when deploying a new application on a PaaS is to click "add database". Do that nine times and you have nine Postgres containers on one box, each holding its own shared buffers, its own WAL writer and its own connection-handling processes, serving a workload that would not trouble a single instance.

What it actually costs

A Postgres container reserves memory whether or not it is doing anything. On an 8 GB machine running a control plane, an invoicing tool, two marketing sites and a fleet platform, per-app database containers spend most of the RAM on idle duplication. The same machine with one shared instance runs comfortably with headroom for the application containers that are doing the actual work.

The trade you are actually making

Sharing an instance means sharing a failure domain and a maintenance window. That is a real cost and it should be stated plainly: a restart affects everything. For a portfolio operated by one team on one box, that is the correct trade — the alternative is not nine independent failure domains, it is nine things competing for the same finite memory on the same host, which fails together anyway and more confusingly.

It stops being the right trade the moment two workloads have genuinely different availability requirements, or a customer contract puts data isolation in writing.

The provisioning protocol

Sharing safely needs a rule that is followed every time:

  1. Never provision a new database container from the control panel.
  2. Open a shell into the primary instance and create a dedicated database for the new service.
  3. Give the service its own connection string pointing at that database over the internal Docker network. Nothing is exposed publicly.
  4. Point the application at it through environment variables, never a committed file.

The migration trap

One detail costs an afternoon the first time it bites. An application with a formal migrations directory should start with prisma migrate deploy. An application that has only ever used db push must not: on a database whose schema is already populated, migrate deploy exits with P3005 — the database schema is not empty, and the container restart-loops. The start command has to match how the schema was created, and that fact belongs in the runbook rather than in someone's memory.

Next step

Tell us what is broken or what should exist.

Send the shape of the problem and any constraints you already know — budget, deadline, the stack you are stuck with. You will get a written reply from the engineer who would do the work, not a sales sequence.