When deploying multiple Ruby on Rails applications, choosing between containerized (Docker/Kamal) and traditional (Nginx/Passenger) deployment strategies can significantly impact your server resource requirements. Let's break down the real memory footprint of each approach.
The Contenders
We'll compare three deployment strategies for hosting multiple Rails apps with SQLite3 on a single server:
-
Docker + Kamal - Modern containerized deployment
-
Traditional (Capistrano + Nginx + Puma) - Separate processes per app
-
Traditional (Nginx + Passenger) - Copy-on-Write optimization
Memory Breakdown: Traditional Deployment
Typical Stack: Nginx + Puma + SQLite3
Component RAM Usage Notes
| Nginx | 10-30 MB | Shared across all apps
| Ruby process | 80-150 MB | Base Ruby VM
| Rails app (loaded) | +100-200 MB | Per app
| Puma workers (4) | +400-800 MB | 4 × (Ruby + app)
| SQLite3 | 0-50 MB | In-process, minimal overhead
| OS overhead | 200-400 MB | Shared services
Single Rails App Memory
Nginx (10 MB) + Puma master (150 MB) + 4 workers (4 × 150 MB) = ~760 MB
The Passenger Advantage: Copy-on-Write
This is where traditional deployment shines. Passenger uses Copy-on-Write (CoW) to share memory pages between application instances:
Nginx (10 MB) + Passenger core (50 MB) + 4 app instances with CoW
First instance: 150 MB (full)
Instances 2-4: +50 MB each (shared memory pages)
Total: ~360 MB instead of 760 MB
Head-to-Head: 3 Rails Apps on One Server
Memory Comparison
Deployment Type RAM per App Total RAM (3 apps) Disk Space
| Docker + Kamal | 500-650 MB | ~1.8 GB | ~1 GB (images)
| Traditional (separate Puma) | 600-800 MB | ~2.1 GB | ~300 MB (code)
| Passenger (CoW) | 300-400 MB | ~1 GB | ~300 MB (code)
Detailed Memory Breakdown
Option A: Docker + Kamal
App A container: 550 MB
App B container: 600 MB
App C container: 500 MB
Traefik: 60 MB
─────────────────────────
Total: ~1.7 GB
Option B: Traditional (Capistrano + Nginx + separate Puma per app)
Nginx: 20 MB (shared)
App A (Puma): 700 MB
App B (Puma): 750 MB
App C (Puma): 680 MB
─────────────────────────
Total: ~2.15 GB
Option C: Traditional (Nginx + Passenger with CoW)
Nginx: 20 MB
Passenger: 50 MB
App A (4 workers): 350 MB (CoW shared)
App B (4 workers): 320 MB (CoW shared)
App C (4 workers): 300 MB (CoW shared)
─────────────────────────
Total: ~1.04 GB ⭐ WINNER
Key Insights
1. Passenger is Most Memory-Efficient
Copy-on-Write sharing means the Ruby VM and loaded gems are shared across workers, saving 50-70% RAM compared to isolated processes. This makes it ideal for hosting many small applications on a single server.
2. Docker Overhead: ~50-100 MB per Container
Each container includes runtime overhead for network namespacing and isolation. While not enormous, it adds up when running multiple applications.
3. SQLite3 is Nearly Free
Since SQLite3 runs in-process with no separate database server, memory overhead is minimal—typically just in-memory caching for active datasets.
4. Disk Space Advantage: Traditional Deployment
Traditional: 300 MB (Ruby + gems in system paths)
Docker: ~1 GB (3 images with layer sharing)
Capacity Planning
On a 2 GB RAM Server
Deployment Max Apps Why
| Passenger | 4-5 apps | CoW memory sharing
| Docker/Kamal | 2-3 apps | Isolation overhead
| Separate Puma | 2 apps | No memory sharing
On a 4 GB RAM Server
Deployment Max Apps Why
| Passenger | 8-10 apps | Still most efficient
| Docker/Kamal | 5-6 apps | Comfortable headroom
| Separate Puma | 4-5 apps | Works but wasteful
The Verdict
Memory Champion: Passenger with Copy-on-Write uses 40-50% less RAM than Docker-based deployments.
Choose Docker/Kamal When:
- You need strong isolation between apps (different Ruby versions, conflicting dependencies)
- You want zero-downtime deploys and instant rollbacks
- You have adequate RAM (4+ GB for multiple apps)
- You value deployment consistency across environments
- You're deploying to multiple servers or cloud platforms
Choose Traditional (Passenger) When:
- Running many small applications on limited RAM
- Apps are stable and don't require frequent rollbacks
- All apps can share the same Ruby version
- Working with a tight budget (e.g., $4-6/month VPS with 2 GB RAM)
-
Maximum resource efficiency is the priority
Real-World Recommendation
For Rails applications using SQLite3 where memory is constrained, Passenger delivers unmatched efficiency. However, if you need the operational benefits of containerization and have sufficient resources, Docker with Kamal provides excellent developer experience and deployment reliability.
The best choice depends on your specific priorities: resource efficiency versus operational convenience. Both are valid approaches—choose based on your constraints and requirements.
Have you compared these deployment strategies in your own projects? What memory usage patterns did you observe? Share your experiences in the comments below.
NOTE: Article update/revised partially generated by latest AP services Grok/Claude
(note for primitive assholes who have issues with using AI in writing)