In Strapi, a multitenant approach refers to the ability of the system to serve multiple tenants or customers, each with its own isolated and independent set of resources such as content, users, and permissions. It allows you to create multiple application instances using a single codebase. In this way, all clients have a separate database, theming, and separate domain for each Strapi instance. The advantage of running multitenant is you can roll out the new feature easily to all clients easily and it involves low maintenance with a scalable solution.
This tutorial will show how to run multiple Strapi applications sharing the same code. This is not the complete approach for multitenancy. But it solves some problems, like rolling the same features to multiple websites simultaneously. I have been running around ten applications, such as driving school, jewelry shop, personal portfolio, and educational consultancy.
The approach is simple. I have a separate database for each instance or website, but they all run from a single code base. So it somehow acts as a multi-tenant. I have been able to manage different themes for each client but can roll out the same features for all instances at once.
Prerequisites
- nginx
- Let’s Encrypt for SSL
- MySQL Database
- phpMyAdmin (DB management )
- Netlify ( For Frontend )
- Node.js v16+
- Ubuntu 20.04
Create a Strapi Application
Create a Strapi project running the following command.
npx create-strapi-app@latest strapi-multitenancy
Once all the dependencies are installed, it should be up and running at http://localhost:1337/
By default, Strapi runs on the SQLite database. Stop the app and let’s change the database to MySQL.
Add MySQL
Navigate to the directory where you created strapi-multitenancy (or your custom project name). Then run the following command to add the MYSQL package.
npm install mysql --save
Changing Strapi Config
Initially, the project structure and config folder should look like this:
Suppose we want to run three website development, site1, and site2. Then the new config structure should look like the one below:
By default, all the files inside the directory are the same as the files being created.
Creating MySQL Database
I assume that you have a MySQL server running. Let's create three databases:
- development
- site1
- site2
Keep the database username and password safe for later use.
Update Database Configuration
After creating the database and config directory, let's modify the database configuration.
The default database.js file is as below.
const path = require('path');
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
},
useNullAsDefault: true,
},
});
We want to change this to MySQL and each website specific. In my environment, the database username is root and the database password is root.
Now you will update site1 and site2 as below.
The database config for site1:
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'site1'),
user: env('DATABASE_USERNAME', 'root'),
password: env('DATABASE_PASSWORD', 'root'),
ssl: env.bool('DATABASE_SSL', false),
},
},
});
The database config for site2:
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'site2'),
user: env('DATABASE_USERNAME', 'root'),
password: env('DATABASE_PASSWORD', 'root'),
ssl: env.bool('DATABASE_SSL', false),
},
},
});
Update Host Configuration
Now we need to specify different host ports for running three applications. The default server.js file is as below, which we need to update.
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
});
Let’s modify for app host. This is a configuration for app site1:
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('HOST_PORT_SITE1', 4338),
app: {
keys: env.array('APP_KEYS'),
},
});
This is a configuration for app site2:
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('HOST_PORT_SITE2', 4339),
app: {
keys: env.array('APP_KEYS'),
},
});
Install PM2 Runtime
We will be using PM2 for running our Strapi application.
Install PM2 by running:
npm install pm2@latest -g
The pm2 configuration file must be located at the root level and the filename as ecosystem.config.js.
The pm2 configuration is as below.
module.exports = {
apps: [
{
name: 'site1',
cwd: '/Users/bikramkawan/Bikram/strapi-multitenancy-v4',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'site1',
HOST_PORT_SITE1: 4338,
DOMAIN_URL: 'site1.example.com'
}
},
{
name: 'site2',
cwd: '/Users/bikramkawan/Bikram/strapi-multitenancy-v4',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'site2',
HOST_PORT_SITE2: 4339,
DOMAIN_URL: 'site2.example.com'
}
}
]
};
From the above configuration, you need to change the correct path where your application is:
- cwd -> Change to the directory where you create the Strapi application
- Database credentials
- Host ports
- DOMAIN_URL is for a fully qualified domain like strapimultitest1.example.com
Testing on Local Environment (localhost)
At this point, the app should be running on your local environment you are doing on your local machine. It should accessible via localhost or either public IP.
Run the app running this command on the root:
pm2 start ecosystem.config.js
Ideally, you should be able to access http://localhost:4338/ and http://localhost:4339/.
For more documentation on pm2, you can read from here
Configuring nginx for Live (Production)
Based on your distro, you need to install nginx correctly. I am running ubuntu 20.04. Please feel free to follow any documentation for installing nginx on your distro. I followed the DigitalOcean article for installing nginx.
It is important to make sure that port 80 and port 443 (for SSL) as open. After successfully installing, we will configure the correct file.
Go to cd /etc/nginx/sites-available, then create the file for site1.example.com using vi strapimultitest1.example.com. Simply vi /etc/nginx/sites-available/site1.example.com and paste the following. Change the corresponding port depending on your application server 127.0.0.1:4338;
upstream site1.example.com {
server 127.0.0.1:4338;
keepalive 64;
}
server {
server_name site1.example.com;
access_log /var/log/nginx/site1.example.com-access.log;
error_log /var/log/nginx/site1.example.com-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://site1.example.com;
proxy_redirect off;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
}
}
server {
listen 80;
server_name site1.example.com;
}
server {
server_name site1.example.com;
listen 80;
return 404;
}
We need to create another file for site2 as:
upstream site2.example.com {
server 127.0.0.1:4339;
keepalive 64;
}
server {
server_name site2.example.com;
access_log /var/log/nginx/site2.example.com-access.log;
error_log /var/log/nginx/site2.example.com-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://site2.example.com;
proxy_redirect off;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
}
}
server {
listen 80;
server_name site2.example.com;
}
server {
server_name site2.example.com;
listen 80;
return 404;
}
After creating nginx config file then, run:
sudo ln -s /etc/nginx/sites-available/site1.example.com /etc/nginx/sites-enabled/site1.example.com
sudo ln -s /etc/nginx/sites-available/site2.example.com /etc/nginx/sites-enabled/site2.example.com
Check if nginx config is ok by running:
sudo nginx -t
Restart nginx:
sudo systemctl restart nginx
If port 80 is open, both applications site1.example.com and site2.example.com should be accessible.
Install SSL Certificate
Let's secure our application and install SSL certificate. Check the Strapi documentation to install SSL certificate.
Run the following command:
sudo apt install certbot python3-certbot-nginx -y
Now we need to add our domains:
certbot --nginx -d site1.example.com -d site2.example.com
It should now be running SSL on both of the domains.
Conclusion
In this tutorial, we learn how to create a multitenant on Strapi. This means you can run multiple Strapi instances with separate domains and separate databases. But with the same code base. From this approach, we will save time on maintaining applications and easy to roll out new features easily.
Here is the GitHub repo I have created for the approach you can test on your own at localhost or production.
You can join the Discord community to connect with more people using Strapi. You can ask for help or get answers to your question from the community.
Let me know if you run into an issue in the comments. Thank you!
Top comments (1)
Thanks this is a great idea - exactly the logic i need for my use case - but the caveat is you can't really have separate dev environments for each site can you? I tried to implement it, it works as such - and i also configured ecosystem.config.js to optionally start in development environments - but as it seems there is only one overarching development environment - no matter if i start site1 or site2 or the development website in development mode. That means i cannot make specific changes only to site1 or site2 - every change "bleeds-over" to the other app too. Is this the intended use of this solution or am i missing something?? Sheding some light on this would be highly appreciated - been trying to figure this out for weeks now!! Thanks in advance.