Repository: matrix-org/synapse

Issue:
Having 4 different Docker images with different ways of being configured is repetitive and confusing
We have:

* 'the image' as published on Docker Hub (this one is user-facing and we don't want to break the interface)
* a workers variant (I don't know who uses this other than Complement..?)
* a Complement variant (SQLite only!)
* a Complement + Postgres + workers variant

They don't even configure themselves in the same way. For example, one of them uses ugly `sed` incantations to configure appservices, the other two use slightly different Jinja2 (though they also have good reason for being slightly different, it still seems like they could be unified).

It seems like, as a first step to restore some sanity, we could for example remove the Complement+SQLite image and make it a special case of the workers one?
This would also open up the doors to having Complement+Postgres (without workers).

I will have a look at this to try and see about unifying them. I will probably leave the user-facing image alone.

New idea?:
* 'the image'
* Workers + Postgres
    * Complement + Workers + Postgres
        * Complement + monolith + Postgres (or can use SQLite via env var?); special case of 'no workers'


Discussion:
> a workers variant (I don't know who uses this other than Complement..?)

ISTR @MatMaul may have used it for something?
> we could for example remove the Complement+SQLite image

One thing about this is that, because the Complement+SQLite image is a pretty light modification to the base Synapse image, it gives some confidence that our base image starts and runs, which isn't necessarily true for the full-on Complement+Postgres+workers image.

That said, I suspect that's a fairly illusory safety net, and if that's something we actually want to test, we should do it a different way.

In short: your idea sounds good.
> * 'the image'

Unclear how Pyston will fit into this: see also #12850
> > * 'the image'
> 
> Unclear how Pyston will fit into this: see also #12850

That's a good point. I wonder how tricky it'd be to make Pyston a build argument.

One thing I've used recently is https://earthly.dev/ (I think Dan mentioned it a long time ago). It's kind of a build tool that uses Docker as its way to do things; I think of it as 'Dockerfile with multiple build targets, dependencies between them and if statements'.
If we need a way to address all the variations, it might be a useful tool, so we don't copy and paste the same image definition loads of times with minor variations.

Of course, it's yet another tool to use (and to go wrong), so it's only an idle thought rather than a proper suggestion.
> I wonder how tricky it'd be to make Pyston a build argument.

I'd rather see us ship only one interpreter in our docker images. We have enough on our plate as-is; no reason to double our state space.
> I'd rather see us ship only one interpreter in our docker images. We have enough on our plate as-is; no reason to double our state space.

That's certainly one option, but does this mean that we'll have to choose between running Complement tests against Pyston (which we will maybe be using on m.org) or against CPython (which I think we would be somewhat unwilling to drop support for)?

I suppose you're saying 'ship'; I certainly have no objection to having only one image we dish out to admins, but it's still useful for development/testing tasks to be able to customise it a bit. I don't know how extensive the changes are to make our Dockerfile compatible with Pyston, but if it's not too extensive, I still think it'd be best to keep it as an argument.

Resolving patch:
diff --git a/docker/configure_workers_and_start.py b/docker/configure_workers_and_start.py
--- a/docker/configure_workers_and_start.py
+++ b/docker/configure_workers_and_start.py
@@ -37,8 +37,8 @@
 from pathlib import Path
 from typing import Any, Dict, List, Mapping, MutableMapping, NoReturn, Set
 
-import jinja2
 import yaml
+from jinja2 import Environment, FileSystemLoader
 
 MAIN_PROCESS_HTTP_LISTENER_PORT = 8080
 
@@ -236,12 +236,13 @@ def convert(src: str, dst: str, **template_vars: object) -> None:
         template_vars: The arguments to replace placeholder variables in the template with.
     """
     # Read the template file
-    with open(src) as infile:
-        template = infile.read()
+    # We disable autoescape to prevent template variables from being escaped,
+    # as we're not using HTML.
+    env = Environment(loader=FileSystemLoader(os.path.dirname(src)), autoescape=False)
+    template = env.get_template(os.path.basename(src))
 
-    # Generate a string from the template. We disable autoescape to prevent template
-    # variables from being escaped.
-    rendered = jinja2.Template(template, autoescape=False).render(**template_vars)
+    # Generate a string from the template.
+    rendered = template.render(**template_vars)
 
     # Write the generated contents to a file
     #
@@ -378,8 +379,8 @@ def generate_worker_files(
     nginx_locations = {}
 
     # Read the desired worker configuration from the environment
-    worker_types_env = environ.get("SYNAPSE_WORKER_TYPES")
-    if worker_types_env is None:
+    worker_types_env = environ.get("SYNAPSE_WORKER_TYPES", "").strip()
+    if not worker_types_env:
         # No workers, just the main process
         worker_types = []
     else:
@@ -506,12 +507,16 @@ def generate_worker_files(
             if reg_path.suffix.lower() in (".yaml", ".yml")
         ]
 
+    workers_in_use = len(worker_types) > 0
+
     # Shared homeserver config
     convert(
         "/conf/shared.yaml.j2",
         "/conf/workers/shared.yaml",
         shared_worker_config=yaml.dump(shared_config),
         appservice_registrations=appservice_registrations,
+        enable_redis=workers_in_use,
+        workers_in_use=workers_in_use,
     )
 
     # Nginx config
@@ -531,6 +536,7 @@ def generate_worker_files(
         "/etc/supervisor/supervisord.conf",
         main_config_path=config_path,
         worker_config=supervisord_config,
+        enable_redis=workers_in_use,
     )
 
     # healthcheck config


Test patch: