Adding CPU and Memory Limits to the Postgres Service in lavanderia‑crm’s Docker Compose
TL;DR: I added mem_limit and cpus to the Postgres container in docker-compose.yml to prevent it from hogging resources on my dev machine. The change stabilizes the local environment and avoids OOM kills without impacting production.
The Problem
Running the full stack of lavanderia‑crm on a laptop with 8 GB RAM, I kept hitting Docker’s out‑of‑memory protection. The Postgres container would sporadically get killed, producing logs like:
docker: Error response from daemon: container ... exceeded its memory limit
In practice this manifested as intermittent FATAL: could not connect to server: Connection refused errors in the API layer. The symptom was clear: the database was consuming more RAM and CPU than the host could safely provide, especially when running heavy seed scripts during development.
What I Tried First
My first instinct was to limit resources at runtime with docker run flags, e.g.:
docker run -d --name pg_test \
--memory=512m --cpus=1 \
postgres:16-alpine
That works for a single container, but our workflow relies on docker-compose up to spin up the entire stack (frontend, backend, Postgres, Redis). Trying to pass the same flags through docker-compose using the command key didn’t help; Docker ignored them because the Compose file version (2.1) didn’t expose resource constraints at the service level.
I also experimented with the newer deploy.resources block:
deploy:
resources:
limits:
cpus: "1"
memory: 512M
Unfortunately, deploy is ignored by Docker Compose in non‑Swarm mode, so the limits never applied. After a few failed attempts, I went back to the docs and found the legacy mem_limit and cpus keys that still work for Compose version 2.x on a local dev machine.
The Implementation
1. Update docker-compose.yml
I added two lines directly under the Postgres service definition:
services:
db:
image: postgres:16-alpine
container_name: lavanderia_crm_db
restart: unless-stopped
mem_limit: 512m # <‑‑ new line
cpus: "1" # <‑‑ new line
environment:
POSTGRES_USER: ...
POSTGRES_PASSWORD: ...
POSTGRES_DB: lavanderia
The diff from the commit (d5d860c2) looks like this:
@@ -3,6 +3,8 @@ services:
image: postgres:16-alpine
container_name: lavanderia_crm_db
restart: unless-stopped
+ mem_limit: 512m
+ cpus: "1"
environment:
POSTGRES_USER:
2. Why those values?
-
mem_limit: 512m– Caps RAM at 512 MiB. The database never exceeds this, which is sufficient for our dev seed data (≈200 MiB) while leaving headroom for the API and UI containers. -
cpus: "1"– Limits the container to one CPU core. Postgres is I/O‑bound in our use case, so throttling CPU prevents it from starving other services during bulk inserts.
3. Verify the limits
After docker-compose up -d, I inspected the container:
docker inspect lavanderia_crm_db --format='{{json .HostConfig.Memory}}' | jq .
# => 536870912 (bytes = 512 MiB)
docker inspect lavanderia_crm_db --format='{{json .HostConfig.NanoCpus}}' | jq .
# => 1000000000 (1 CPU)
Running a heavy migration script now stays within the allocated memory, and the container no longer gets killed.
4. Adjusting for Production
For production we use a separate Compose file (docker-compose.prod.yml) that omits these limits, letting the orchestrator (Kubernetes) enforce resource policies. Keeping the dev limits isolated prevents accidental promotion of restrictive settings.
5. Full docker-compose.yml (relevant excerpt)
version: "2.4"
services:
db:
image: postgres:16-alpine
container_name: lavanderia_crm_db
restart: unless-stopped
mem_limit: 512m
cpus: "1"
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: lavanderia
volumes:
- db_data:/var/lib/postgresql/data
api:
build: ./api
depends_on:
- db
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/lavanderia
volumes:
db_data:
Key Takeaway
When developing locally with Docker Compose, use the legacy mem_limit and cpus keys (Compose 2.x) to enforce hard resource caps. They’re simple, don’t require Swarm mode, and protect your dev machine from runaway containers—especially useful for stateful services like databases that can otherwise dominate RAM.
What's Next
I plan to automate the generation of environment‑specific Compose files with a small Node script that swaps the mem_limit/cpus block based on a DEV=true flag. This will keep the repository clean and make onboarding new developers a one‑command experience.
Roberto Luna Osorio – Full Stack Developer & Project Lead
Playa del Carmen, México
vibecoding #buildinpublic #docker #postgres #devops #docker-compose
Part of my Build in Public series — sharing the real process of building Building Lavandería CRM from Playa del Carmen, México.
Repo: zaerohell/lavanderia-crm · 2026-10-11
#playadev #buildinpublic
Top comments (0)