Skip to main content

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

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:

  1. Validates domain/subdomain availability
  2. Generates secure secrets (JWT, API tokens, admin JWT, transfer token)
  3. Creates isolated Strapi app copy with production builds
  4. Generates SSL certificate (mkcert or Let's Encrypt)
  5. Creates Docker Compose configuration
  6. Configures Traefik routing rules
  7. Adds domain to /etc/hosts (local) or validates DNS (production)
  8. Starts Docker containers
  9. 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 strapi user 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

Support

For issues or questions: