Why My NestJS App Crashes on VPS After Simple Deployment – 3 Hidden Docker Config Mistakes You Need to Fix Right Now
You finally pushed your NestJS masterpiece to a cheap VPS, hit docker-compose up -d, and—boom!—the container dies within seconds. No logs, no clues, just a silent crash that wastes hours of debugging.
If you’ve ever felt that punch of frustration, you’re not alone. The biggest culprits are usually a handful of Docker settings that look harmless on the surface but explode in production.
Why This Matters
Every minute your API is down means lost requests, unhappy users, and a dent in revenue. In a competitive SaaS market, uptime is a trust signal. Fixing these hidden Docker pitfalls not only stabilizes your NestJS service but also saves you from costly support tickets and sleepless nights.
The 3 Sneaky Docker Config Mistakes
1️⃣ Ignoring Memory Limits (and Swallowing OOM Killer)
Docker defaults to “unlimited” memory unless you set mem_limit. On a low‑tier VPS (1‑2 GB RAM) your NestJS process can easily exceed the available memory, causing the Linux OOM Killer to terminate the container without a clear error.
2️⃣ Using node:latest Image Without Pinning Versions
Pulling node:latest each deploy introduces unpredictable changes. A new Node version might drop support for native modules compiled against an older V8, leading to immediate crashes that look like “random” failures.
3️⃣ Forgetting to Set ENV NODE_ENV=production Inside Docker
When NODE_ENV stays on development, NestJS loads the heavy @nestjs/config loader, hot‑reloading file watchers, and debug‑level logging. Those extra processes eat CPU and memory—perfect fuel for the OOM killer.
npm run start:prod and never rely on the default npm run start script.
Step‑by‑Step Fix – Deploy a Crash‑Free NestJS Docker Container
-
Pin the Node base image. Replace
node:latestwith a specific LTS tag, e.g.,node:20-alpine.FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY . . RUN npm run build -
Explicitly set memory limits. Add
mem_limitordeploy.resources.limits.memoryindocker‑compose.yml.services: api: build: . ports: - "3000:3000" environment: - NODE_ENV=production deploy: resources: limits: memory: 500M restart: unless-stopped -
Set
NODE_ENV=productionand use the production start script.# package.json snippet "scripts": { "start": "nest start", "start:prod": "node dist/main", "build": "nest build" }Make sure your
Dockerfileruns the prod script:FROM node:20-alpine AS runtime WORKDIR /app COPY --from=builder /app/dist ./dist COPY package*.json ./ ENV NODE_ENV=production CMD ["npm","run","start:prod"] -
Turn off source‑map and debug logging. In
src/main.tsadd a guard:if (process.env.NODE_ENV !== 'production') { app.useLogger(['log', 'error', 'warn', 'debug', 'verbose']); } else { app.useLogger(['log', 'error', 'warn']); } -
Enable health checks. Docker will automatically restart a healthy container.
# Add to docker-compose.yml healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 5s retries: 3
Real‑World Use Case: E‑Commerce API on a $5 DigitalOcean Droplet
Imagine you run a small shop that needs a fast product catalog API. You spin up a 1 GB Droplet, install Docker, and deploy the NestJS container using the steps above.
Before the fix:
- CPU spiked to 100 % during traffic spikes.
- Container terminated after 45 seconds, no logs.
After applying the three hidden fixes:
- Memory stayed under 350 MB (well within the 500 MB limit).
- Uptime > 99.9 % over a month, even with flash‑sale traffic.
- Response time dropped from 250 ms to 90 ms because the production build removes dev‑only middleware.
Results / Outcome
By locking the Node version, enforcing memory caps, and running in true production mode, you eliminate the “random crash” syndrome. The benefits are tangible:
- Reduced support tickets: 0 crashes reported in the first week.
- Higher conversion: 2 % boost in completed purchases during peak load.
- Lower costs: You stay on the $5 plan instead of upgrading to $10.
Bonus Tips – Polish Your Docker Deployment
- Use multi‑stage builds. Keeps the final image tiny (≈30 MB).
- Leverage .dockerignore. Exclude
node_modules,testfolders, and.gitto speed up builds. - Enable Read‑Only Filesystem. Add
read_only: truein compose to prevent accidental writes. - Log to stdout only. Docker captures logs automatically; avoid writing to files inside the container.
- Set a proper restart policy.
restart: unless-stoppedensures your API survives a server reboot.
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Monetization Opportunity (Optional)
If you’re building APIs for clients, package this hardened Docker setup as a “Deploy‑Ready NestJS Starter.” Charge a one‑time fee ($49) or a monthly maintenance retainer. The reliability you provide is a premium service that many startups are willing to pay for.
Ready to stop random crashes and start scaling profitably? Implement the three hidden fixes today, and watch your NestJS app run like a charm on any cheap VPS.
No comments:
Post a Comment