Cookiecutter zope instance and ZEO

I am moving my production environment from buildout with plone.recipe.zope2instance to this cookiecutter for Plone 6 and want to continue to use ZEO. I have a vew questions.

  1. I noticed that there is no zeo.conf created. Is that outside the scope of this cookiecutter template? I didn't see a template for ZEO so I looked at the plone-zeo Dockerfile to grab its zeo.conf and modified it for my purposes. It looks like I should install zeo to my venv and then have systemd or supervisor use the runzeo command with that conf. This seems to work, but a cookiecutter setup would be a lot nicer if it exists.

  2. I am not sure if I should be using shared or cache for db_storage_mode. In my old plone.recipe.zeoserver I have shared-blob set to true, is this the same thing?

  3. I assume all of the Direct filestorage options, like Data.fs, don't apply if I'm using ZEO and I'll need to set these in my zeo.conf (or in another cookiecutter template), is that true?

has some info

1 Like

I've been playing around with this and noticed a discrepancy with ZEO. When I start ZEO for the first time and no existing Data.fs exists at the target storage, it creates a new one. However the acl_users object is different than if I had created it with a direct WSGI client. It's created as a "User Folder" instead of a "Pluggable Auth Service" object.

I tried mounting a ZODB Mount Point that contains a Plone Site on this, and migrating to Plone 6. This fails because a step is looking at the plugins in acl_users which only exist if it's a PAS object. It seems some step is missing when creating a new DB with ZEO - it may be more than just this acl_users, I don't know.

Try creating a new Plone site, then deleting it, before you migrate the Plone site inside the mountpoint. Converting the root-level acl_users from a User Folder to Pluggable Auth Service is one of the things that happens during creation of a Plone site.

1 Like

I noticed that the environment variable for this cookiecutter template does not work as I would expect it. I want to add some more env variables but only CHAMELEON_CACHE and zope_i18n_compile_mo_files are updated. I dug into cookiecutter a bit and it seems that this check prevents anything other than those keys being used because those are the only ones defined in the

Is there any other way to do this? Being able to set some env variables seems like a common use case.

On my wishlist would be to add support for mounted dbs, to append something like this to zope.conf

<zodb_db db01>
    cache-size 5000
    allow-implicit-cross-references false
      blob-dir /plndata/blobs-db01
      shared-blob-dir on
      server localhost:8100
      storage db01
      name db01_zeostorage
      cache-size 30MB
    mount-point /db01

I'll start by forking the cookiecutter-zope-instance package and testing against my own template

Pull requests are welcome. May you please create an issue here Issues · plone/cookiecutter-zope-instance · GitHub ?

[edit] Yesterday I already fixed a related problem fixes for cookiecutter 2.2 · plone/cookiecutter-zope-instance@11b1d5b · GitHub

The cookiecutter covers only Zope instance configuration. The ZEO server is not covered.

Then keep it this way. Shared implies usage of a shared folder for the blobs on each zeo-client machine. This is the same as old shared-blob. The new cookiecutter uses (where possible) the same terms (sometimes prefixed) as the Zope Configuration documentation uses. The old buildout-recipe introduced its own terms.


Absolutely. I planned to do a full write up on work on Monday now that I have everything working. I added an option for "mounted_dbs" that might be worth adding. I used to do this with collective.recipe.filestorage in buildout. Also made a very minimal ZEO template It is by no means an attempt to replicate all of plone.recipe.zeoserver, it just does the minimum of what I needed.

1 Like

I have this mostly working on a dev environment and wanted to share my experience. This is how I set up the python environment and the configs needed to run ZEO/WSGI and all of the database management tools (everything that was in buildout in Plone 5). For some of our systems we have ZODB Mount Points containing a single site per database. So those are ~10-12 Plone Sites running on the same ZEO/Zope.

  1. Install Plone and ZEO in python venv. For this dev environment I also used mxdev to install packages in edit mode. We have a lot of in house packages that have small, specific scopes and would generally want to be able to edit them at the same time. mxdev is designed for that purpose.

  2. Instead of using the mkwsgiinstance and mkzeoinstance scripts, I used cookiecutter templates to build the config files needed for the WSGI clients and ZEO server. We will probably have a lot of very similar YAML files that only differ by port number. To be fair, that was easier to manage in buildout because buildout configs could inherit from each other.

This fork of cookiecutter-zope-instance GitHub - ewohnlich/cookiecutter-zope-instance: It bakes configuration for Zope 5 adds some default env variables due to a bug with defaults of dict data types and adds "mounted_dbs" as an option. I'll make separate MRs for each. ZODB Moint Point management was previously handled in buildout with collective.recipe.filestorage. There's no cookiecutter template for that recipe, and I don't know if it even makes sense to have two separate templates that both need to modify zope.conf, so I included it here. I also created a minimal ZEO template GitHub - ewohnlich/cookiecutter-zeo-server. This does not try to do everything that plone.recipe.zeoserver did, it is just enough for my own needs. Creating a custom cookiecutter template was easy to do and easy to use.

I had the ZEO template use the same yaml file. This is my instance1.yaml that builds alpha_zeo/etc and alpha_client1/etc and configures the var areas.

  target: 'alpha_client1'
  zeo_target: 'alpha_zeo'

  wsgi_listen: $SERVER:8083
    "CHAMELEON_CACHE": "{{ cookiecutter.location_clienthome }}/cache"
    "ENTREZ_EMAIL": ""

  initial_user_name: 'admin'
  initial_user_password: 'admin'

  db_storage: zeo
  db_blobs_mode: shared
  db_filestorage_location: $FILEBASE/alpha/filestorage
  mounted_dbs: db01,db02

  debug_mode: false
  1. I can now run the ZEO server with "runzeo -C alpha_zeo/etc/zeo.conf" and run WSGI clients with "runwsgi alpha_client1/etc/zope.ini". TODO: daemonize with supervisord. This is what I used before, and in fact I used buildout to create the supervisor.conf. I plan to have one supervisor for the server (multiple ZEO servers and WSGI clients). If possible, it would be nice if cookiecutter can be setup to read from multiple YAML files. Otherwise I'll just create the conf manually.

  2. For backup and recovery I created bash scripts per site/db using repozo and rsync. This example is a dev site where I don't actually do backups, but we do want a "recovery" script that pulls from production.


repozo --recover -o $FILESTORAGE_DIR/Data.fs -r $BACKUP_BLOBSTORAGE_DIR


repozo --recover -o $FILESTORAGE_DIR/Data.fs -r $BACKUP_FILESTORAGE_DIR

I just made this manually but this seems like something that would be easily created with cookiecutter, maybe even putting that into cookiecutter-zeo-server (can it create a dynamic number of script files?). Also TODO: parameterize a timestamp. Newer Zope allows blob backups to be saved with a timestamp so I believe it should be trivial to pass that timestamp to repozo and to refer to e.g. blobstorage-db01.2023-06-10/blobstorage-db01 as the source location for blobs.

  1. For database packing, I originally looked at CLI options but decided to instead use the through-the-web option. The reasoning is simple - it's already built through the web and I don't risk accidentally misconfiguring somehow. This python script reads from the yaml file:
from imsplone.utils import get_password
import requests
import yaml
import os

username = 'admin_cron'
pw = get_password('admin_cron')

def main():
    with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'instance1.yaml')) as file:
        config = yaml.safe_load(file)['default_context']
        data = {
            'days:float': 1,

        # Pack main db
        url = f'http://{config["wsgi_listen"]}/Control_Panel/Database/main/manage_pack', data=data, auth=(username, pw))

        # Also pack all mounted dbs
        for db in config['mounted_dbs'].split(','):
            url = f'http://{config["wsgi_listen"]}/Control_Panel/Database/{db}/manage_pack'
  , data=data, auth=(username, pw))

if __name__ == '__main__':

I think that's everything we were using buildout for, now moved to a couple yaml files and scripts. The python virtual environment is completely separated from this whole process. I did see that cookiecutter-zope-instance has some ZCML settings but I didn't see a purpose for this in my setup - perhaps this is only for packages that aren't pip installed?

Thanks for your work on all this @jensens !