Multi-Instance Strapi Manager with Docker and Traefik
Overview
This guide explains how to deploy and manage multiple isolated Strapi instances on a VPS using our custom Strapi Instance Manager CLI. Each instance runs in its own Docker container with dedicated database, SSL certificate, and Traefik routing.
The Instance Manager automates the entire lifecycle: creating instances, managing SSL certificates, configuring Traefik routes, handling backups/restores, and monitoring instance health.
Use this when you need to host multiple Strapi projects (for different clients, environments, or tenants) on a single VPS with automatic SSL and isolated resources.
Architecture
VPS Server
├── Traefik (Reverse Proxy)
│ ├── HTTPS → Instance 1 (blog.domain1.com)
│ ├── HTTPS → Instance 2 (shop.domain2.com)
│ └── HTTPS → Instance 3 (api.domain3.com)
├── PostgreSQL (Shared or per-instance)
├── Instance Manager CLI
│ ├── Create/Delete instances
│ ├── Start/Stop/Restart lifecycle
│ ├── Backup/Restore data
│ ├── Health monitoring
│ └── SSL management (mkcert/Let's Encrypt)
└── Instances Directory
├── app-instance1/ (isolated Strapi files)
├── app-instance2/
├── instance1.yml (Docker Compose config)
├── instance2.yml
└── .env.instance1 (secrets)
Assumptions & Prerequisites
- VPS with modern Linux distro (Ubuntu 22.04 LTS or Debian 11+ recommended)
- Root or sudo access for initial setup
- Docker and Docker Compose installed
- Node.js v20+ installed (for CLI management)
- Domain names with DNS A records pointing to VPS IP
- At least 2GB RAM (4GB+ recommended for multiple instances)
Installation
1. Create a system user for management
We recommend creating a dedicated user for managing Strapi instances. Unlike giving docker group access (which grants effective root privileges), this user will use sudo for Docker commands, providing better security and audit trails.
As root or a sudo-enabled user:
# Create a regular user with password
adduser strapi
# Grant sudo privileges (user will type password for Docker/admin commands)
usermod -aG sudo strapi
# Verify sudo access
su - strapi
sudo whoami # Should output: root (after entering password)
Security Note: We intentionally do NOT add the user to the docker group. This requires typing your password for privileged operations, which:
- Creates an audit trail in
/var/log/auth.log - Prevents accidental root-level operations
- Allows granular sudo permissions if needed
- Follows principle of least privilege
2. Setup SSH access (optional but recommended)
As root, copy SSH keys to the new user:
mkdir -p /home/strapi/.ssh
cp ~/.ssh/authorized_keys /home/strapi/.ssh/
chown -R strapi:strapi /home/strapi/.ssh
chmod 700 /home/strapi/.ssh
chmod 600 /home/strapi/.ssh/authorized_keys
Test SSH login before proceeding:
# From your local machine
ssh strapi@your-vps-ip
3. Install Docker and Docker Compose
# As strapi user
sudo apt update && sudo apt upgrade -y
# Install essential utilities
sudo apt-get install mailutils -y
sudo apt-get update && sudo apt-get install ncdu -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Install Docker Compose plugin
sudo apt install -y docker-compose-plugin
# Verify installation
sudo docker --version
sudo docker compose version
4. Install Node.js
# Install nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Reload shell configuration
source ~/.bashrc
# Install Node.js LTS
nvm install --lts
nvm use --lts
# Verify
node --version # Should show v20.x or v22.x
npm --version
5. Clone and setup the Instance Manager
# Create workspace directory
mkdir -p ~/strapi-manager
cd ~/strapi-manager
# Clone repository (replace with your actual repo URL)
git clone https://github.com/team-ledges/Flo.STRAPI.git .
# Navigate to manager directory
cd manager
# Install CLI dependencies
npm install
# Verify CLI works
node cli.js --help
6. Configure firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status
Configuration
Traefik Setup
The Instance Manager automatically sets up Traefik with the following structure:
traefik/
├── traefik.yml # Main Traefik configuration
├── dynamic/
│ ├── tls.yml # SSL certificate definitions
│ └── middlewares.yml # Rate limiting, headers, etc.
└── certs/ # mkcert certificates (local/staging)
└── letsencrypt/ # Let's Encrypt certs (production)
For production with real SSL certificates, update traefik/traefik.yml:
certificatesResolvers:
letsencrypt:
acme:
email: admin@yourdomain.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
For local/staging with mkcert, the CLI handles certificate generation automatically.
Database Configuration
Each instance can use:
- Shared PostgreSQL (default): Multiple databases on one Postgres container
- Dedicated PostgreSQL: Each instance gets its own Postgres container
- SQLite: For testing/development only
Default configuration uses shared Postgres defined in compose.yml.
CLI Usage
Create a new instance
cd ~/strapi-manager/manager
# Interactive mode (prompts for domain/subdomain)
sudo node cli.js create
# Direct mode with arguments
sudo node cli.js create mydomain.com blog
# Creates:
# - URL: https://blog.mydomain.com
# - Container: strapi-mydomain-com
# - Database: strapi_mydomain-com
# - Config: instances/mydomain-com.yml
# - App copy: instances/app-mydomain-com/
What happens during creation:
- Validates domain/subdomain availability
- Generates secure secrets (JWT, API tokens, admin JWT, transfer token)
- Creates isolated Strapi app copy with production builds
- Generates SSL certificate (mkcert or Let's Encrypt)
- Creates Docker Compose configuration
- Configures Traefik routing rules
- Adds domain to
/etc/hosts(local) or validates DNS (production) - Starts Docker containers
- Creates PostgreSQL database
List all instances
sudo node cli.js list
# Output:
# 📦 Strapi Instances
#
# 🟢 blog.mydomain.com
# ID: mydomain-com
# Status: RUNNING
# Database: strapi_mydomain-com
# Created: 10/31/2025, 1:39:59 PM
# URL: https://blog.mydomain.com
Lifecycle management
# Start an instance
sudo node cli.js start mydomain-com
# Stop an instance
sudo node cli.js stop mydomain-com
# Restart an instance
sudo node cli.js restart mydomain-com
# Check instance health
sudo node cli.js health mydomain-com
# View instance status
sudo node cli.js status mydomain-com
# Stream instance logs
sudo node cli.js logs mydomain-com
Backup and restore
# Create backup
sudo node cli.js backup mydomain-com
# Creates: backups/mydomain-com_20251031_143000.tar.gz
# Includes: database dump, uploads, config files
# List backups
ls -lh backups/
# Restore from backup
sudo node cli.js restore mydomain-com backups/mydomain-com_20251031_143000.tar.gz
SSL certificate management
# List all SSL certificates
sudo node cli.js ssl list
# Add/renew certificate for domain
sudo node cli.js ssl add blog.mydomain.com
# Remove certificate
sudo node cli.js ssl remove blog.mydomain.com
Delete an instance
# Delete instance but keep database
sudo node cli.js delete mydomain-com
# Delete instance AND database (destructive!)
sudo node cli.js delete mydomain-com --data
Complete reset (development only)
# WARNING: Deletes ALL instances, containers, and data
sudo node cli.js hard-reset
Directory Structure
After creating instances, your workspace will look like:
~/strapi-manager/
├── manager/
│ ├── cli.js # Main CLI entry point
│ ├── instances.json # Instance registry
│ ├── commands/ # CLI commands
│ ├── services/ # Docker, Traefik, DB services
│ └── lib/ # Utilities (crypto, logger, validators)
├── app/ # Base Strapi application (template)
├── instances/
│ ├── app-mydomain-com/ # Isolated Strapi files for instance
│ ├── mydomain-com.yml # Docker Compose config
│ └── .env.mydomain-com # Environment secrets
├── traefik/
│ ├── traefik.yml # Traefik main config
│ ├── dynamic/
│ │ └── tls.yml # SSL certificates
│ └── certs/ # Certificate files
├── backups/ # Instance backups
└── postgres-data/ # PostgreSQL data (if using shared DB)
Production Deployment Example
Step-by-step: Deploy three instances
# 1. SSH into VPS as strapi
ssh strapi@your-vps-ip
# 2. Navigate to manager
cd ~/strapi-manager/manager
# 3. Create first instance (blog)
sudo node cli.js create example.com blog
# Wait ~2 minutes for build to complete
# Access: https://blog.example.com/admin
# 4. Create second instance (shop)
sudo node cli.js create example.com shop
# Access: https://shop.example.com/admin
# 5. Create third instance (API for mobile app)
sudo node cli.js create api.example.com www
# Access: https://www.api.example.com/admin
# 6. Verify all running
sudo node cli.js list
# 7. Check Traefik dashboard (if enabled)
curl http://localhost:8080/api/http/routers
DNS Configuration
For the above example, configure these DNS records:
A blog.example.com <vps-ip>
A shop.example.com <vps-ip>
A www.api.example.com <vps-ip>
Or use wildcard DNS:
A *.example.com <vps-ip>
Monitoring and Maintenance
View container status
# All containers
sudo docker ps
# Specific instance
sudo docker ps -f name=strapi-mydomain-com
# Container resource usage
sudo docker stats
View logs
# Stream logs for specific instance
sudo node cli.js logs mydomain-com
# Or directly with Docker
sudo docker logs -f strapi-mydomain-com
# Traefik logs
sudo docker logs -f traefik
Database access
# Connect to PostgreSQL
sudo docker exec -it postgres psql -U strapi
# List databases
\l
# Connect to specific instance database
\c strapi_mydomain-com
# List tables
\dt
System health checks
# Check all instances health
cd ~/strapi-manager/manager
for instance in $(sudo node cli.js list --json | jq -r '.[].id'); do
echo "Checking $instance..."
sudo node cli.js health $instance
done
# Disk usage
df -h
sudo docker system df
# Memory usage
free -h
Automated backups
Create a backup script:
nano ~/backup-instances.sh
#!/bin/bash
BACKUP_DIR="/home/strapi/strapi-manager/backups"
DATE=$(date +%Y%m%d)
cd ~/strapi-manager/manager
# Backup all instances
for instance in $(sudo node cli.js list --json 2>/dev/null | jq -r '.[].id'); do
echo "Backing up $instance..."
sudo node cli.js backup $instance
done
# Cleanup old backups (keep last 7 days)
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
echo "Backup completed: $DATE"
Make executable and schedule:
chmod +x ~/backup-instances.sh
# Add to crontab
crontab -e
# Add this line (runs daily at 2 AM)
0 2 * * * /home/strapi/backup-instances.sh >> /home/strapi/backup.log 2>&1
Troubleshooting
Permission denied for Docker commands
Symptom: permission denied while trying to connect to the Docker daemon socket
Solution: Add sudo before commands or configure sudoers for passwordless Docker (not recommended):
# View command with sudo
sudo docker ps
Instance won't start
# Check container status
sudo docker ps -a | grep mydomain-com
# View container logs
sudo docker logs strapi-mydomain-com
# Common issues:
# - Port already in use: Check with `sudo netstat -tulpn | grep :1337`
# - Database connection failed: Verify Postgres is running
# - Build failed: Check Node.js version in Dockerfile
Traefik routing not working
# Check Traefik configuration
sudo docker logs traefik | grep ERROR
# Verify labels are set correctly
sudo docker inspect strapi-mydomain-com | grep traefik
# Check dynamic configuration
cat ~/strapi-manager/traefik/dynamic/tls.yml
# Restart Traefik
sudo docker restart traefik
SSL certificate issues
# Regenerate certificate
sudo node cli.js ssl add blog.mydomain.com
# Check certificate files exist
ls -la ~/strapi-manager/traefik/certs/
# For Let's Encrypt issues, check email and domain validation
sudo docker logs traefik | grep acme
Database connection failed
# Check Postgres is running
sudo docker ps | grep postgres
# Test database connection
sudo docker exec -it postgres psql -U strapi -c "\l"
# Verify instance database exists
sudo docker exec -it postgres psql -U strapi -c "\l" | grep mydomain-com
# Check database credentials in instance config
cat ~/strapi-manager/instances/.env.mydomain-com
Out of disk space
# Check disk usage
df -h
# Clean Docker resources
sudo docker system prune -a --volumes
# Remove old backups
sudo rm -f ~/strapi-manager/backups/*.tar.gz
# Check largest directories
sudo du -sh ~/strapi-manager/* | sort -h
Security Best Practices
Use sudo instead of docker group
By NOT adding users to the docker group, you ensure:
- All Docker operations require password authentication
- Audit trail in system logs (
/var/log/auth.log) - No accidental root-level access
- Better security posture for production systems
Disable root SSH login
After verifying your user account works:
sudo nano /etc/ssh/sshd_config
Set:
PermitRootLogin no
PasswordAuthentication no # If using SSH keys
Restart SSH:
sudo systemctl restart sshd
Configure fail2ban
Protect against brute-force attacks:
sudo apt install -y fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Use strong secrets
The CLI automatically generates cryptographically secure secrets for:
- JWT tokens
- Admin JWT secret
- API tokens
- Transfer tokens
- Database passwords
Never commit .env.* files to version control.
Regular updates
# Update system packages
sudo apt update && sudo apt upgrade -y
# Update Docker images
sudo docker pull strapi/strapi:latest
sudo docker pull postgres:14-alpine
sudo docker pull traefik:v2.10
# Rebuild instances with new images (if needed)
sudo node cli.js restart mydomain-com
Performance Optimization
Increase file descriptor limits
sudo nano /etc/security/limits.conf
Add:
* soft nofile 65536
* hard nofile 65536
Configure Docker logging
sudo nano /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2"
}
Restart Docker:
sudo systemctl restart docker
Add swap space (if RAM < 4GB)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Quick Reference
Common commands
# Create instance
sudo node cli.js create domain.com subdomain
# List instances
sudo node cli.js list
# Start/stop
sudo node cli.js start <instance-id>
sudo node cli.js stop <instance-id>
# Backup/restore
sudo node cli.js backup <instance-id>
sudo node cli.js restore <instance-id> <backup-file>
# Logs and monitoring
sudo node cli.js logs <instance-id>
sudo node cli.js health <instance-id>
sudo node cli.js status <instance-id>
# Delete
sudo node cli.js delete <instance-id> [--data]
File locations
- CLI:
~/strapi-manager/manager/cli.js - Instances:
~/strapi-manager/instances/ - Backups:
~/strapi-manager/backups/ - Traefik config:
~/strapi-manager/traefik/ - Instance registry:
~/strapi-manager/manager/instances.json
Checklist for New VPS Setup
- Create
strapiuser with sudo access - Setup SSH key authentication
- Configure firewall (ports 22, 80, 443 only)
- Install Docker and Docker Compose
- Install Node.js v20+
- Clone Instance Manager repository
- Configure DNS records for domains
- Setup SSL certificates (Let's Encrypt for production)
- Deploy first test instance
- Configure automated backups
- Install fail2ban
- Disable root SSH login
- Test instance creation, start/stop, backup/restore
References
- Docker Installation: https://docs.docker.com/engine/install/
- Traefik Documentation: https://doc.traefik.io/traefik/
- Strapi Documentation: https://docs.strapi.io/
- Let's Encrypt: https://letsencrypt.org/
- PostgreSQL Docker: https://hub.docker.com/_/postgres
Support
For issues or questions:
- GitHub Repository: https://github.com/team-ledges/Flo.STRAPI
- Check logs:
sudo node cli.js logs <instance-id> - View container status:
sudo docker ps -a - Traefik dashboard: http://localhost:8080 (if enabled)