This guide covers everything from local development to production deployment of EmailEngine using Docker.
- Docker Engine 20.10.0+ and Docker Compose 2.0.0+
- System Requirements:
- Development: 4GB RAM minimum
- Production: 8GB RAM minimum, 16GB+ recommended
- Critical Notes:
- No Docker resource limits are imposed
- Redis has NO memory limit and uses 'noeviction' policy (database mode)
- MUST monitor memory usage to prevent system OOM
- Monitoring: Use
docker statsand Redis INFO memory regularly
# Clone or download EmailEngine
git clone https://github.com/postalsys/emailengine.git
cd emailengine
# Start with development settings
cp .env.development .env
docker-compose upAccess EmailEngine at http://localhost:3000
Development mode features:
- No authentication required
- Services accessible from any network
- Debug logging enabled
- Minimal resource requirements
# Use automated setup script
./setup-production.sh
# Or manually:
cp .env.production .env
nano .env # Add secure passwords (see Configuration section)
docker-compose up -dProduction mode features:
- Redis authentication required
- Services bound to localhost only
- Optimized logging and persistence
- Health checks and resource limits
EmailEngine uses a single docker-compose.yml that adapts based on environment variables in .env.
| File | Purpose | Security | Use Case |
|---|---|---|---|
.env.development |
Local development | None | Testing, development |
.env.production |
Production deployment | Full | Live systems |
| Setting | Development | Production |
|---|---|---|
| Network Binding | 0.0.0.0 (all interfaces) |
127.0.0.1 (localhost only) |
| Redis Password | None | Required |
| Secret Key | Default | Must generate |
| Health Checks | Relaxed | Strict |
| Logging | Debug | Info |
| System Requirements | Minimal | Production-grade |
# Generate secure credentials
EENGINE_SECRET=$(openssl rand -hex 32)
REDIS_PASSWORD=$(openssl rand -base64 32)
# Configure in .env
EENGINE_SECRET=<generated-secret>
REDIS_PASSWORD=<generated-password>
EMAILENGINE_API_BIND=127.0.0.1 # Never expose directly
# Note: Redis has no memory limit and uses 'noeviction' policy
# This is intentional - EmailEngine uses Redis as a database# Create application directory
sudo mkdir -p /opt/emailengine
cd /opt/emailengine
# Copy files
# - docker-compose.yml
# - .env.production
# - setup-production.sh
# Generate credentials
./setup-production.shNever expose EmailEngine directly to the internet. Use a reverse proxy with TLS.
server {
listen 443 ssl http2;
server_name emailengine.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
# Long timeouts for email operations
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
# WebSocket support
location /socket.io/ {
proxy_pass http://127.0.0.1:3000/socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name emailengine.example.com;
return 301 https://$server_name$request_uri;
}emailengine.example.com {
reverse_proxy localhost:3000
header Strict-Transport-Security "max-age=31536000"
header X-Frame-Options "SAMEORIGIN"
header X-Content-Type-Options "nosniff"
}# Only allow necessary ports
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (redirect)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
# Verify internal services aren't exposed
sudo netstat -tuln | grep -E '3000|2525|9993' # Should show 127.0.0.1 onlydocker-compose up -d
docker-compose ps # Verify all services are healthy# Application health
curl http://localhost:3000/health
# Redis connectivity
docker-compose exec redis redis-cli -a "$REDIS_PASSWORD" ping
# Monitor resource usage (important: no limits are set)
docker stats --no-stream
# Continuous monitoring
docker stats
# Recent errors
docker-compose logs --tail=100 emailengine | grep ERROR- CPU usage < 80%
- Memory usage < 90%
- Redis memory usage < max configured
- API response times
- Queue lengths
#!/bin/bash
# /opt/emailengine/backup.sh
BACKUP_DIR="/backup/emailengine"
DATE=$(date +%Y%m%d-%H%M%S)
# Load environment
source /opt/emailengine/.env
# Trigger Redis save
docker-compose exec -T redis redis-cli -a "$REDIS_PASSWORD" BGSAVE
sleep 5
# Backup data
docker run --rm \
-v emailengine_redis-data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/redis-$DATE.tar.gz -C /data .
# Backup config
tar czf $BACKUP_DIR/config-$DATE.tar.gz .env docker-compose.yml
# Keep 30 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -deleteAdd to crontab:
0 2 * * * /opt/emailengine/backup.sh# Pull latest version
docker-compose pull
# Rolling restart
docker-compose up -d --no-deps emailengine
# Verify
docker-compose logs --tail=50 emailengine# Specify previous version in .env
EMAILENGINE_VERSION=2.40.0
docker-compose up -d --no-deps emailengineImportant: EmailEngine is resource-intensive and will use available system resources.
| Accounts | Min RAM | Recommended RAM | CPU Cores | Workers |
|---|---|---|---|---|
| < 100 | 8GB | 16GB | 4 | 4 |
| 100-500 | 16GB | 32GB | 8 | 8 |
| 500-2000 | 32GB | 64GB | 16 | 16 |
| 2000+ | 64GB | 128GB+ | 24+ | 24 |
# Adjust in .env based on your needs:
# Number of worker processes (4-24)
EENGINE_WORKERS=8
# Redis configuration
# Redis is used as a DATABASE, not a cache:
# - No memory limit is set (uses available system memory)
# - Eviction policy is 'noeviction' to prevent data loss
# - Monitor usage: docker-compose exec redis redis-cli INFO memory- EmailEngine will automatically use available CPU and RAM
- No artificial limits are imposed by default
- Redis operates as a database with no memory limits and no eviction
- Workers should match CPU cores (minimum 4, maximum 24)
Since resource limits are not enforced:
- Critical: Monitor Redis memory usage regularly
docker-compose exec redis redis-cli INFO memory - Use system monitoring tools (htop, top, docker stats)
- Set up alerts for high memory usage (especially important for Redis)
- Ensure sufficient swap space as a safety measure
- Consider using monitoring solutions like Prometheus + Grafana
# Check configuration
docker-compose config
# Verify .env file
grep -E "EENGINE_SECRET|REDIS_PASSWORD" .env
# Check ports
netstat -tuln | grep -E '3000|2525|9993'# Check actual memory usage (no container limits set)
docker stats --no-stream
# Check Redis memory (critical - Redis has no memory limit)
docker-compose exec redis redis-cli -a "$REDIS_PASSWORD" INFO memory
# Redis memory metrics to monitor:
# - used_memory_human: Total memory used by Redis
# - used_memory_rss_human: Memory used from OS perspective
# - mem_fragmentation_ratio: Should be close to 1.0
# If memory usage is too high, you may need to:
# 1. Add more RAM to the system
# 2. Scale horizontally (multiple EmailEngine instances)
# 3. Review and optimize EmailEngine configuration# Test Redis connection
docker-compose exec emailengine redis-cli -h redis -a "$REDIS_PASSWORD" ping
# Check logs
docker-compose logs --tail=100 redis
docker-compose logs --tail=100 emailengine# Enable debug logging temporarily
docker-compose exec emailengine sh -c 'export EENGINE_LOG_LEVEL=debug'
docker-compose restart emailengine
# Check slow operations
docker-compose exec redis redis-cli -a "$REDIS_PASSWORD" SLOWLOG GET 10# Stop services
docker-compose down
# Restore data
docker run --rm \
-v emailengine_redis-data:/data \
-v /backup:/backup \
alpine tar xzf /backup/redis-latest.tar.gz -C /data
# Start services
docker-compose up -d- Strong EENGINE_SECRET (32+ characters)
- Strong REDIS_PASSWORD (32+ characters)
- Services bound to localhost only
- Reverse proxy with TLS configured
- Firewall enabled (ports 80/443 only)
- Backup automation configured
- Memory monitoring in place (critical - Redis has no limits)
- Log rotation enabled
- Sufficient system RAM for Redis growth
- Swap space configured as safety measure
- Rotate API keys quarterly
- Update Docker images monthly
- Review access logs weekly
- Test backup restoration monthly
- Security scan quarterly
# Status
docker-compose ps
docker-compose logs --tail=50
# Restart
docker-compose restart emailengine
# Stop/Start
docker-compose stop
docker-compose start
# Full restart
docker-compose down
docker-compose up -d
# Update
docker-compose pull
docker-compose up -d
# Backup
docker-compose exec redis redis-cli -a "$REDIS_PASSWORD" BGSAVE- Configuration:
/opt/emailengine/.env - Compose file:
/opt/emailengine/docker-compose.yml - Redis data: Docker volume
emailengine_redis-data - Logs:
docker-compose logs
# To development
cp .env.development .env
docker-compose restart
# To production
cp .env.production .env
# Edit .env with secure values
docker-compose restart- Documentation: https://emailengine.app/
- API Reference: https://api.emailengine.app/
- Issues: https://github.com/postalsys/emailengine/issues