Multitenancy on Development environment

Hello, I’m pretty new to web development and Frappe, and I’m trying to grasp the concept of multi-tenancy better. I want to clarify if multi-tenancy is even possible in development mode.

I tried setting up 2 sites on a single bench (all still in development mode) although I’m using nginx outside of Bench to publish the site. I tried to setup a port-based multitenancy to run both sites by tinkering with the Procfile as shown below.

//other settings
web: bench serve --port 8000
web2: bench serve --port 8001
//other settings

When I ran bench start, it does run both ports and I’m able to access both domains. However, the problem is, both domains will point to the site listed in the currentsite.txt file, so the result is I have 2 domains pointing to 1 site because currentsite.txt only accepts 1 site (as far as I have tested).

What I wanted is to have 2 sites running simultaneously, and the 2 domains I have will each point to a different site (domain1 point to siteA, while domain2 point to siteB).

I have tried many solutions to work around this as I read that multi-tenancy expects sites to be ran on production mode, and neither bench serve nor bench start have an option to run a specific site.

So, what I want to clarify is:

  1. Is multi-tenancy only works for sites in production mode as it relies on bench’s nginx and supervisor?
  2. Is the only solution to this is to setup a new bench instance if I want to run both sites simultaneously on development mode?

I’ll appreciate it if anyone have any knowledge about this that you can share with me as I still have many holes in my understanding of Frappe that I’m trying to fill :blush:

1 Like

You should create two sites with this command and the appriate options (e.g. your ports – or maybe not ports, after all, since you can differentiate the sites via your domain names):

https://frappeframework.com/docs/user/en/bench/reference/new-site

so this will will create two site-config.json, one for each site, which you can then fine-tune if necessary.

Name the sites according to the domain they shall serve.

Hi @rp21:

Try

bench use site1.local
bench serve --port 8001

Open other terminal and

bench use site2.local
bench serve --port 8002

This works but … what about 2 different benches?
Hope this helps.

Thank you, this solution works and I figured out some of the limitations of running 2 sites on the same bench when trying out your solution (the important one is I can’t seem to keep both sites running by sending the bench serve processes to the background)

I have now used 2 different benches to run 2 sites simultaneously as you suggested and it’s able to keep running when the processes is sent to the background

It could work with DNS based multi-tenancy. I’ve been doing it for a few years now. You do need nginx or other equivalent web servers for this though.

Just create *.localhost name servers in nginx config. Like erpnext1.localhost and erpnext2.localhost. With these, you don’t need to add etc host file mappings. *.localhost always resolve to 127.0.0.1. You have to pass correct X-Frappe-Site-Name header so that frappe properly resolve to specific site. Here a sample config from my development setup.

upstream frappe-v14-frappe {
	server 127.0.0.1:9004 fail_timeout=0;
}
upstream frappe-v14-socketio-server {
	server 127.0.0.1:9003 fail_timeout=0;
}
server {
	listen 80;
	server_name erpnext14.localhost;

	root /home/frappe/frappe-v14/sites;

	proxy_buffer_size 128k;
	proxy_buffers 4 256k;
	proxy_busy_buffers_size 256k;

	add_header X-Frame-Options "SAMEORIGIN";
	add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
	add_header X-Content-Type-Options nosniff;
	add_header X-XSS-Protection "1; mode=block";
	add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin";

	location /assets {
		try_files $uri =404;
	}

	location ~ ^/protected/(.*) {
		internal;
		try_files /erpnext14.localhost/$1 =404;
	}

	location /socket.io {
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header X-Frappe-Site-Name erpnext14.localhost;
		proxy_set_header Origin $scheme://$http_host;
		proxy_set_header Host $host;

		proxy_pass http://frappe-v14-socketio-server;
	}

	location / {

 		rewrite ^(.+)/$ $1 permanent;
  		rewrite ^(.+)/index\.html$ $1 permanent;
  		rewrite ^(.+)\.html$ $1 permanent;

		location ~* ^/files/.*.(htm|html|svg|xml) {
			add_header Content-disposition "attachment";
			try_files /erpnext14.localhost/public/$uri @webserver;
		}

		try_files /erpnext14.localhost/public/$uri @webserver;
	}

	location @webserver {
		proxy_http_version 1.1;
		proxy_set_header X-Forwarded-For $remote_addr;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header X-Frappe-Site-Name erpnext14.localhost;
		proxy_set_header Host $host;
		proxy_set_header X-Use-X-Accel-Redirect True;
		proxy_read_timeout 120;
		proxy_redirect off;

		proxy_pass  http://frappe-v14-frappe;
	}

	# error pages
	error_page 502 /502.html;
	location /502.html {
		root /home/nayminlwin/.pyenv/versions/3.11.5/lib/python3.11/site-packages/bench/config/templates;
		internal;
	}

	access_log  /var/log/nginx/access.log;
	error_log  /var/log/nginx/error.log;

	# optimizations
	sendfile on;
	keepalive_timeout 15;
	client_max_body_size 50m;
	client_body_buffer_size 16K;
	client_header_buffer_size 1k;

	gzip on;
	gzip_http_version 1.1;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_proxied any;
	gzip_vary on;
	gzip_types
		application/atom+xml
		application/javascript
		application/json
		application/rss+xml
		application/vnd.ms-fontobject
		application/x-font-ttf
		application/font-woff
		application/x-web-app-manifest+json
		application/xhtml+xml
		application/xml
		font/opentype
		image/svg+xml
		image/x-icon
		text/css
		text/plain
		text/x-component
		;
		# text/html is always compressed by HttpGzipModule
}

Try this in Procfile:

web: bench serve --port 8000 //By default added
web2: bench --site sitename.local serve --port 8001 //change the key to web2 any increment, change the port to something different
web3: bench --site sitename2.local serve --port 8002

Add site.name to host file and it should be accessible on browser using the following url:
sitename.local:8001
sitename2.local:8002

This setup will work without adding custom domain. Is there any point adding custom domain in dev setup?