Build1 distinct publisher3 min readUpdated
A Chatwoot operator's postmortem shows the meter you inherit watches Postgres while attachments pile up on a Docker volume that grows with tenant count, not traffic.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
An operator running self-hosted Chatwoot as the WhatsApp inbox for about a dozen small Israeli businesses got a disk alert at 86 percent and went looking at the database, which he says was the wrong place [s1 c1][2]. Postgres was 680 MB; the `chatwoot_storage_data` volume was 17 GB [3]. That is roughly twenty-five times the database, sitting in a place the default instinct never checks [1].
The reason is structural, not a bug. Attachments live in ActiveStorage on a Docker volume, so every image, voice note and PDF a customer sends is a file on disk and none of it appears in a database size query [4]. If your monitoring watches the DB, it reports healthy right up to the moment the container cannot write [5].
The accounting failure compounds the capacity failure. According to the author, the growth curve is a function of how many accounts you host, not how busy any one of them is [6]. His volume grew at roughly 0.05 GB a month until he onboarded seven new businesses over two months, after which it hit 16 GB a month [7]. That is a factor of about 320 in growth rate, triggered by sales, not by load [2]. Anyone forecasting storage from conversation volume will be wrong by two orders of magnitude on the month they close a deal.
Then there is what is actually on the volume. When he measured, almost half the outbound media was byte-identical duplicates [9]. One 14.5 MB video was stored 48 separate times; one image 325 times [10]. Chatwoot creates a new blob and a new file on every send even when the bytes match, which he calls correct behaviour for a chat app where every message owns its attachment, and expensive the moment anything fans one file out to many conversations [11]. In his case the fan-out was not campaigns but his own drip engine, re-reading and re-uploading the same file per conversation [12]. Forty-eight copies of a 14.5 MB video is 696 MB on disk to hold 14.5 MB of information [3].
He deduplicated after reading the Rails source, on the basis that `ActiveStorage::Blob#purge` is guarded by a foreign key on `active_storage_attachments.blob_id`, so removing one message's blob does not delete a file other messages still reference [13]. One pass matching on `checksum` and `byte_size` within the same account reclaimed 3.20 GB across 7,160 blobs and moved the disk from 86 percent to 71 percent [14]. That is about 19 percent of the volume recovered without deleting a single customer-visible message [4]. He recommends an index on `active_storage_blobs (checksum, byte_size)` and a size floor first, so you are not doing checksum lookups on 4 KB thumbnails [15].
The same measurement error shows up on the send path. `POST /api/v1/conversations/{id}/messages` returns 200 as soon as it inserts a row with `status = 0` and no `source_id`; actual delivery happens later in a Sidekiq job, and the WhatsApp message ID is written only when Meta acknowledges [16]. Four resend runs pushed 5,108 messages through that endpoint, the `high` queue reached 3,786 jobs with eleven minutes of latency, and inbound messages from paying customers queued behind them [17]. Nothing errored [18].
What to watch: whether your dashboard has a series for the storage volume at all, and whether your capacity plan is keyed to tenant count rather than message count [3][6]. His backpressure fix counts his own undelivered rows every 40 sends and pauses above 250 until the backlog drops under 80, which measures pressure he created rather than a global number he does not control [19]. And before any inbox deletion, note that his deleted inbox took 894 conversations and 6,160 messages with it, while contacts survived because they live at the account level [20].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The author runs self-hosted Chatwoot as the WhatsApp inbox for a dozen or so small Israeli businesses, on two servers, handling a few thousand conversations a week, with a drip-sequence engine bolted on the side.
The author got a disk alert at 86 percent and immediately went looking at the database, which he says was the wrong place.
Measured sizes: Postgres database 680 MB; the chatwoot_storage_data Docker volume 17 GB.
Attachments live in ActiveStorage on a Docker volume, not in Postgres; every image, voice note and PDF a customer sends is a file on disk and none of it shows up when you check database size.
If monitoring watches the database, it will report that everything is fine right up until the container cannot write.
Storage growth sat at roughly 0.05 GB a month until the author onboarded seven new businesses over two months, and then it hit 16 GB a month.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Specific first-hand telemetry, single unverified source
The account is unusually concrete for a practitioner post: exact volume sizes, blob and message counts, queue depth and latency, dedup yield, and a quoted ActiveStorage 7.1.5.2 method that substantiates the safety argument. What holds it back is that every figure is self-reported from one deployment, there is no second publisher, no vendor confirmation, and the article text is truncated mid-way through the restore section.
One multi-tenant deployment, no broader uptake evidence
Adoption evidence is limited to a single operator's production estate — two servers, about a dozen tenant accounts, a few thousand conversations a week — plus the remediations applied inside it. Nothing in the cluster speaks to how widely Chatwoot self-hosting, the dedup approach, or the backpressure pattern is used elsewhere, so this reflects one verified real deployment rather than diffusion.
Findings sober; the month-six generalization outruns n=1
The mechanisms and numbers are stated modestly and the author repeatedly assigns fault to himself, which is the opposite of hype. The overstatement is in scope rather than substance: framing five failures from one dozen-tenant Chatwoot install as what happens to self-hosted support platforms at month six, and asserting that growth is a function of account count rather than per-account traffic, generalizes further than a single unverified deployment can carry.
Practitioner credibility incentive, no disclosed vendor stake
The supplied text shows a self-published first-person operator post with no sponsorship, product pitch, pricing offer, or claimed affiliation with Chatwoot, Meta or any hosting vendor, and it praises the software it criticizes. The residual incentive is reputational: the piece establishes the author's expertise in running WhatsApp automation for small businesses, and its custom drip engine and sender are his own work, which gives a mild interest in framing the platform's defaults as the surprising part.
Internally coherent single-witness account
Mechanisms are consistent with how ActiveStorage, Sidekiq and Rails dependent: :destroy cascades are known to behave, and the numbers are mutually coherent (dedup yield fits the reported duplication, disk percentages move as expected). Confidence is held mid-range because there is one publisher, one witness, no vendor or third-party check, self-reported telemetry, and a truncated article body.
build
A RAG stack lived seven hours before a hosted embedding endpoint returned 4041 distinct publisher
build
Three services you can delete: queue, cache and search in one Postgres1 distinct publisher
build
1,254 dead mutants, a 100% score, and a payment charged twice1 distinct publisher
build
A missing WHERE clause, 24 databases, and the case for guards over prompts1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 19, 2026