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.
Create the fresh Strapi 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.
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
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.
I assume that you have a MySQL server running. Let's create three databases:
Keep the database username and password safe for later use.
After creating the database and config directory, let's modify the database configuration. The default database.js file is as below.
1 const path = require('path');
2
3 module.exports = ({ env }) => ({
4 connection: {
5 client: 'sqlite',
6 connection: {
7 filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
8 },
9 useNullAsDefault: true,
10 },
11 });
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:
1 module.exports = ({ env }) => ({
2 connection: {
3 client: 'mysql',
4 connection: {
5 host: env('DATABASE_HOST', 'localhost'),
6 port: env.int('DATABASE_PORT', 3306),
7 database: env('DATABASE_NAME', 'site1'),
8 user: env('DATABASE_USERNAME', 'root'),
9 password: env('DATABASE_PASSWORD', 'root'),
10 ssl: env.bool('DATABASE_SSL', false),
11 },
12 },
13 });
The database config for site2:
1 module.exports = ({ env }) => ({
2 connection: {
3 client: 'mysql',
4 connection: {
5 host: env('DATABASE_HOST', 'localhost'),
6 port: env.int('DATABASE_PORT', 3306),
7 database: env('DATABASE_NAME', 'site2'),
8 user: env('DATABASE_USERNAME', 'root'),
9 password: env('DATABASE_PASSWORD', 'root'),
10 ssl: env.bool('DATABASE_SSL', false),
11 },
12 },
13 });
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.
1 module.exports = ({ env }) => ({
2 host: env('HOST', '0.0.0.0'),
3 port: env.int('PORT', 1337),
4 app: {
5 keys: env.array('APP_KEYS'),
6 },
7 });
Let’s modify for app host. This is a configuration for app site1:
1 module.exports = ({ env }) => ({
2 host: env('HOST', '0.0.0.0'),
3 port: env.int('HOST_PORT_SITE1', 4338),
4 app: {
5 keys: env.array('APP_KEYS'),
6 },
7 });
8
This is a configuration for app site2:
1 module.exports = ({ env }) => ({
2 host: env('HOST', '0.0.0.0'),
3 port: env.int('HOST_PORT_SITE2', 4339),
4 app: {
5 keys: env.array('APP_KEYS'),
6 },
7 });
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.
1 module.exports = {
2 apps: [
3 {
4 name: 'site1',
5 cwd: '/Users/bikramkawan/Bikram/strapi-multitenancy-v4',
6 script: 'npm',
7 args: 'start',
8 env: {
9 NODE_ENV: 'site1',
10 HOST_PORT_SITE1: 4338,
11 DOMAIN_URL: 'site1.example.com'
12 }
13 },
14 {
15 name: 'site2',
16 cwd: '/Users/bikramkawan/Bikram/strapi-multitenancy-v4',
17 script: 'npm',
18 args: 'start',
19 env: {
20 NODE_ENV: 'site2',
21 HOST_PORT_SITE2: 4339,
22 DOMAIN_URL: 'site2.example.com'
23 }
24 }
25 ]
26 };
27
From the above configuration, you need to change the correct path where your application is:
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
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;
1 upstream site1.example.com {
2 server 127.0.0.1:4338;
3 keepalive 64;
4 }
5
6 server {
7 server_name site1.example.com;
8 access_log /var/log/nginx/site1.example.com-access.log;
9 error_log /var/log/nginx/site1.example.com-error.log;
10 location / {
11 proxy_set_header X-Real-IP $remote_addr;
12 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
13 proxy_set_header X-Forwarded-Proto $scheme;
14 proxy_set_header Host $http_host;
15 proxy_set_header X-NginX-Proxy true;
16 proxy_set_header Upgrade $http_upgrade;
17 proxy_set_header Connection "upgrade";
18 proxy_pass http://site1.example.com;
19 proxy_redirect off;
20 proxy_http_version 1.1;
21 proxy_cache_bypass $http_upgrade;
22 }
23 }
24
25 server {
26 listen 80;
27 server_name site1.example.com;
28 }
29
30
31 server {
32 server_name site1.example.com;
33 listen 80;
34 return 404;
35 }
We need to create another file for site2 as:
1 upstream site2.example.com {
2 server 127.0.0.1:4339;
3 keepalive 64;
4 }
5
6 server {
7 server_name site2.example.com;
8 access_log /var/log/nginx/site2.example.com-access.log;
9 error_log /var/log/nginx/site2.example.com-error.log;
10 location / {
11 proxy_set_header X-Real-IP $remote_addr;
12 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
13 proxy_set_header X-Forwarded-Proto $scheme;
14 proxy_set_header Host $http_host;
15 proxy_set_header X-NginX-Proxy true;
16 proxy_set_header Upgrade $http_upgrade;
17 proxy_set_header Connection "upgrade";
18 proxy_pass http://site2.example.com;
19 proxy_redirect off;
20 proxy_http_version 1.1;
21 proxy_cache_bypass $http_upgrade;
22 }
23 }
24
25 server {
26
27 listen 80;
28 server_name site2.example.com;
29
30 }
31
32
33 server {
34 server_name site2.example.com;
35 listen 80;
36 return 404;
37
38 }
After creating nginx config file then, run:
1 sudo ln -s /etc/nginx/sites-available/site1.example.com /etc/nginx/sites-enabled/site1.example.com
2
3 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.
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.
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!
The Strapi experience has always been about flexibility, customization, and open-source innovation. But we understand that managing infrastructure and scaling your application can sometimes be a daunting task, diverting your focus from what you do best: developing web experiences.
That's why we're excited to introduce Strapi Cloud, so you can leverage the same robust Strapi capabilities you love, but without the hassle of managing servers, worrying about uptime, or scaling infrastructure as your project grows. It will allow you to future-proof your apps with a CMS that meets the content management needs of all your stakeholders, no matter your use case, services or devices.
Strapi remains committed to open-source values, and self-hosting will always be a viable option. We believe in offering choices that align with your project's unique needs. Strapi Cloud is an additional resource for those who want to focus solely on development, leaving the infrastructure to us.
Aw are here to support you every step of the way, no matter which hosting option you choose. You can reach out to us and the community on our Discord if you have any questions!
I am a Software Engineer working for a digital learning platform solutions company based in Hobart, Australia. I am the proud father of my daughter Ivy. You can follow me at https://medium.com/@bikramkawan for more articles.