Excellent question — this goes right to the heart of Ghost CMS performance and Node.js architecture.
Let’s break it down clearly and practically for self-hosted Ghost running in Docker 👇
🧩 1. Ghost CMS Architecture and Threading
Ghost CMS is a Node.js application built on Express.js, and by design:
- Node.js is single-threaded per process (non-blocking I/O).
- It uses the event loop to handle many concurrent requests efficiently on one thread.
- Node’s libuv thread pool handles async I/O (file system, network, DNS, etc.), but JavaScript code itself runs on one thread.
👉 So:
A single Ghost process runs on a single CPU core.
Even inside Docker, Ghost uses only one thread (one core) to handle requests.
⚙️ 2. Node.js Concurrency Model
Node.js is designed for high concurrency, not for parallel computation.
It handles:
- 1000s of simultaneous HTTP requests,
- as long as each request doesn’t block the event loop (e.g. long sync loops, heavy CPU work).
Ghost’s workload (rendering posts, DB queries, caching) is mostly I/O-bound, so the single thread performs surprisingly well under typical blog loads.
⚡️ 3. Multi-Core Scaling
Since Node.js is single-threaded, to utilize multiple CPU cores, you must run multiple Ghost processes — each handling requests independently.
There are two main approaches:
Option A — Use Docker Scaling
If you run Ghost in Docker, you can scale horizontally:
docker compose up --scale ghost=4
Each container:
- runs its own Node.js process (1 thread/core)
- shares the same database and content volume
- sits behind a load balancer (e.g., Nginx, Traefik)
✅ Best for:
- Medium/large sites
- 2–8 CPU cores available
- High concurrent traffic
Option B — Use Node Cluster Mode (advanced)
If you don’t use multiple containers, you can use Node’s Cluster module or a process manager (like PM2) to spawn multiple Ghost workers:
pm2 start index.js -i max
-i maxmeans PM2 will start as many instances as CPU cores.- Each instance listens on the same port through a shared master process.
✅ Easier for single-server deployments
⚠️ But Ghost CLI doesn’t natively support cluster mode — so use PM2, systemd, or a load balancer in front.
🧱 4. Docker Performance Considerations
| Factor | Recommendation | Why |
|---|---|---|
| Base image | Use ghost:5-alpine | Lighter, faster startup |
| CPU cores | Assign ≥ 2 cores | Allows scaling / multiple containers |
| Memory | 1–2 GB per container | Avoid GC pauses |
| Storage | Use local SSD volume | Faster DB + image I/O |
| Networking | Use host or bridge mode | Lower latency than overlay |
| Reverse proxy | Use Nginx / Traefik outside container | Enables caching & SSL termination |
Example:
docker run -d \
--name ghost1 \
--cpus=1 \
--memory=1g \
-e url=https://yourdomain.com \
-v ./content:/var/lib/ghost/content \
ghost:5-alpine
🔄 5. Scaling with Nginx or Load Balancer
Typical production setup for multi-core scaling:
┌────────────┐ │ Nginx │ ← reverse proxy + load balancer
└─────┬──────┘
┌────────────┼────────────┐
│ │ │
┌────────────┐ ┌────────────┐ ┌────────────┐ │ ghost-1 │ │ ghost-2 │ │ ghost-3 │
│ (Docker) │ │ (Docker) │ │ (Docker) │
└────────────┘ └────────────┘ └────────────┘
│ │ │
Shared MySQL / Content Volume
Nginx config (simplified):
upstream ghost_upstream { server ghost1:2368; server ghost2:2368; server ghost3:2368;
}server { listen 443 ssl; server_name yourdomain.com; location / { proxy_pass http://ghost_upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
}
}
📊 6. Real-World Performance Benchmarks
| Setup | Req/sec | CPU Usage | Notes |
|---|---|---|---|
| 1 Ghost container (1 core) | ~300–400 | 100% on 1 core | Baseline |
| 4 Ghost containers + Nginx | ~1200–1500 | Spread across 4 cores | Linear scale |
| Ghost + CDN (cached) | >2000 | Minimal | Static hits only |
🚀 7. Summary
| Feature | Behavior |
|---|---|
| Node.js threading | Single thread per Ghost process |
| Multi-core support | Via multiple containers or PM2 cluster |
| Best performance scaling | Nginx + multiple Ghost Docker containers |
| Optimal environment | ghost:alpine, MySQL DB, CDN, gzip & cache |
| CPU-bound tasks | Should be offloaded to worker (not in Ghost) |