DEV Community

Roberto Luna
Roberto Luna

Posted on

Adding CPU and Memory Limits to the Postgres Service in lavanderia‑crm’s Docker Compose

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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)