wecodemore / wpstarter

Easily bootstrap whole site Composer packages for WordPress.
https://wecodemore.github.io/wpstarter/
MIT License
246 stars 35 forks source link

Multi-tenant functionality #94

Closed wunluv closed 2 years ago

wunluv commented 5 years ago

Is your feature request related to a problem? Please describe. I'm always frustrated when Wordpress core has to be downloaded for every project and furthermore contributing to the bloating of php-fpm opcode cache.

Describe the solution you'd like I would like each tenant using a common core which is cached by php-fpm thereby making optimal use of resources and lowering the footprint for 100s of low to medium traffic websites that don't require a dedicated php-fpm pool. Further, I would like a way to share common plugins (possibly via mu-plugins) between tenants and thereby leverage the opcode cache more while allowing for tenants to have their own custom plugins.

Describe alternatives you've considered I've looked at https://github.com/handpressed/wp-multitenancy-boilerplate which is somewhat outdated.

Additional context @gmazzap thanks for this lovely work and really helpful documentation! I am delighted to have come by wpstarter yesterday after over two weeks of working on my own build based off bedrock. I have learnt and am learning so much by reading the documentation of wpstarter and understanding the architecture.

I'm working on a docker based development and production stack and one of my core requirements is multi-tenancy i.e one wordpress core opcode cached, serving multiple tenants. I kindly request any pointers on how I may adapt wpstarter to use one wordpress core.

wunluv commented 5 years ago

On wpstarter 2x a symlinked wp -> /shared/httpd/wp seems to work without issues.

As I continue to read through the docs I'm beginning to see that what I'm asking for might already be built into wpstarter 3x.

gmazzap commented 5 years ago

Hi @wunluv,

First of all thanks for your appreciation.

Then I want to make sure that you've read the documentation of v3, currently in beta, because I don't want to add any new feature to the v2 series, which is maintained but not further developed.

That said, in simple words, a "multi-tenant" installation would be at its core a single installation of WordPress files with a different database.

You would need all plugins and themes needed by all "tenants" in the content folder, and then activate what you are interested in the specific tenant site.

It basically means that as long as you can change the DB-related environment variables (DB_NAME DB_PASSWORD, DB_USER, DB_HOST) you'll be able to use the same WordPress files for multiple, unrelated, "tenants" websites.

In most cases, you could only change DB_NAME, assuming the DB host and user are shared.

So all the "trouble" is reduced to have a way to set DB_NAME differently for each tenant, and a way to distinguish one tenant form the other.

These can be done in several ways.

What looks a "natural" way to me, is doing that at web-server level.

That would allow setting web-server configuration per tenant, which is probably required anyway and can make use of the capability of web servers to setup environment variables, and the capability of WP Starter 3 to read them.

For example, using Apache, would make sense to use virtual hosts tenants sites, and use them to also set the variables:

<VirtualHost *:80>
    ServerAdmin admin@example.com
    DocumentRoot "/path/to/wp-starter/public"
    ServerName tenant1.com
    ServerAlias www.tenant1.com

    SetEnv DB_NAME wp_tenant1
    SetEnv WP_HOME http://www.tenant1.com/
    SetEnv WP_SITEURL http://www.tenant1.com/wp/
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin admin@example.com
    DocumentRoot "/path/to/wp-starter/public"
    ServerName tenant2.com
    ServerAlias www.tenant2.com

    SetEnv DB_NAME wp_tenant2
    SetEnv WP_HOME http://www.tenant2.com/
    SetEnv WP_SITEURL http://www.tenant2.com/wp/
</VirtualHost>

The snippet above configure 2 virtual hosts, each serving a different domain, but both using the same WordPress (WP Starter) path as document root.

Moreover, each virtual host sets 3 environment variables: the database name and the WP_HOME and WP_SITEURL to match host URL.

Basically, that should be all is needed for a "multi-tenancy" WordPress and doesn't require any new features on WP Starter.

If the webserver is nginx, this same result could be obtained using its env directive inside different server blocks.

Every time a new tenant is requested, the operations would be:

At scale this could be even automated with a script in bash, PHP or any other language you're familiar with.

Please note that I never tried this, but I don't any reason why it should not work. If you try it... let me know the outcome :)

gmazzap commented 5 years ago

Something I forgot: of course WP_CONTENT_DIR needs to be set per tenant as well.

kraftner commented 5 years ago

And you need to be sure that your plugins don't try to use their own folder or any other folder outside the content dir for any kind of data storage,cache,... Doing this is of course madness, but I guess you know the plugin ecosystem out there ;)

gmazzap commented 5 years ago

You can (and probably should) prevent any write anywhere but content folder. In theory, plugins and themes should never expect anything but content folder to be writable.

Of course, plugins do crazy things, but a setup like this assumes you have control on all plugins that are installed in all websites... meaning that you can nuke immediately a plugin that does thing it should not do :)

wunluv commented 5 years ago

I'm right in the middle of reading through v3 documentation. Thanks for the link and for the detailed response as well as for the pointers on the WP_CONTENT_DIR

Meanwhile, multi-tenant seems to work out of the box with v3 provided I have a custom config in my shared (symlinked) wp directory.

Here's what I have in wp-config.php

// Tenant specific wp-config path
require $_SERVER['DOCUMENT_ROOT'] . '/wp-config.php';

I'll keep reading through the docs to see if there is a way around this and I'll be sure to update this thread with any developments that will be useful to folk wanting a similar multi-tenant setup.

wunluv commented 5 years ago

I've gone through the documentation and I have a deeper sense of appreciation for the thought that has gone into the design of WPStarter 3x. The level of automation that can be coded in is outstanding!

I use nginx and I hear you about setting most of the environment variables at server level in the site specific vhost files. That's the intention for production.

My current setup is as follows

Currently a composer script is used to symlink to the shared wordpress core:

    "scripts": {
        "post-install-cmd": [
            "@symlink-wp"
         ],
        "symlink-wp": [
            "ln -s /shared/httpd/wp ./htdocs/wp"
        ]
    }

I intend to move this into a more versatile wpstarter custom step once I get the basics figured out.

At this moment I'm stuck with wp-cli that is unable to read $_SERVER environment variables and therefore unable to load the tenant specific wp-config which is pointed to from the shared wordpress core wp-config.php like this:

// Tenant specific wp-config path
require $_SERVER['DOCUMENT_ROOT'] . '/wp-config.php';

Any wp-cli command fails as a result. Do you have any suggestions on better way to point to the wp-config.php of multiple tenants?

franz-josef-kaiser commented 5 years ago

As @schlessera is part of both WP-CLI and wecodemore, he can probably lend a helping hand at this point. :)

wunluv commented 5 years ago

Thanks! Meanwhile, here's two resources which I couldn't get to work nor figure out how the WP-CLI issue was resolved:

gmazzap commented 5 years ago

@wunluv

WP CLI "fakes" $_SERVER['DOCUMENT_ROOT'] when running commands (see https://github.com/wp-cli/wp-cli/blob/8be8a5aca7b3176a0935f5e83b7426cfc786f13f/php/WP_CLI/Runner.php#L254) and the path it uses is what Runner::find_wp_root() returns (see https://github.com/wp-cli/wp-cli/blob/8be8a5aca7b3176a0935f5e83b7426cfc786f13f/php/WP_CLI/Runner.php#L199-L233).

As you can see, it relies on the --path config (https://make.wordpress.org/cli/handbook/config/#global-parameters) if it is present.

WP Starter 3 generates a wp-cli.yml that contains configuration for path parameter, but in your setup it will always points same folder.

So you either find a way to manually pass the --path parameter based on the site you are using, or you need to find an alternative solution.

If you use virtual hosts + env variables, you could set the document root in an env variable and read it, something like this:

$docRoot = $_SERVER['DOCUMENT_ROOT'] ?? getenv('DOCUMENT_ROOT') ?: __DIR__;
require $docRoot . '/wp-config.php';

Hope this helps.

gmazzap commented 2 years ago

Closing this for lack of activity. Maybe we can reopen later. I'm focusing in getting v3 stable now.