diff --git a/docker/complement/Dockerfile b/docker/complement/Dockerfile
new file mode 100644
index 0000000000..4823ce7364
--- /dev/null
+++ b/docker/complement/Dockerfile
@@ -0,0 +1,22 @@
+# A dockerfile which builds an image suitable for testing Synapse under
+# complement.
+
+ARG SYNAPSE_VERSION=latest
+
+FROM matrixdotorg/synapse:${SYNAPSE_VERSION}
+
+ENV SERVER_NAME=localhost
+
+COPY conf/* /conf/
+
+# generate a signing key
+RUN generate_signing_key -o /conf/server.signing.key
+
+WORKDIR /data
+
+EXPOSE 8008 8448
+
+ENTRYPOINT ["/conf/start.sh"]
+
+HEALTHCHECK --start-period=5s --interval=1s --timeout=1s \
+ CMD curl -fSs http://localhost:8008/health || exit 1
diff --git a/docker/complement/README.md b/docker/complement/README.md
new file mode 100644
index 0000000000..e075418e4a
--- /dev/null
+++ b/docker/complement/README.md
@@ -0,0 +1 @@
+Stuff for building the docker image used for testing under complement.
diff --git a/docker/complement/SynapseWorkers.Dockerfile b/docker/complement/SynapseWorkers.Dockerfile
new file mode 100644
index 0000000000..982219a91e
--- /dev/null
+++ b/docker/complement/SynapseWorkers.Dockerfile
@@ -0,0 +1,73 @@
+# This dockerfile builds on top of 'docker/Dockerfile-worker' in matrix-org/synapse
+# by including a built-in postgres instance, as well as setting up the homeserver so
+# that it is ready for testing via Complement.
+#
+# Instructions for building this image from those it depends on is detailed in this guide:
+# https://github.com/matrix-org/synapse/blob/develop/docker/README-testing.md#testing-with-postgresql-and-single-or-multi-process-synapse
+FROM matrixdotorg/synapse-workers
+
+# Download a caddy server to stand in front of nginx and terminate TLS using Complement's
+# custom CA.
+# We include this near the top of the file in order to cache the result.
+RUN curl -OL "https://github.com/caddyserver/caddy/releases/download/v2.3.0/caddy_2.3.0_linux_amd64.tar.gz" && \
+ tar xzf caddy_2.3.0_linux_amd64.tar.gz && rm caddy_2.3.0_linux_amd64.tar.gz && mv caddy /root
+
+# Install postgresql
+RUN apt-get update
+RUN apt-get install -y postgresql
+
+# Configure a user and create a database for Synapse
+RUN pg_ctlcluster 13 main start && su postgres -c "echo \
+ \"ALTER USER postgres PASSWORD 'somesecret'; \
+ CREATE DATABASE synapse \
+ ENCODING 'UTF8' \
+ LC_COLLATE='C' \
+ LC_CTYPE='C' \
+ template=template0;\" | psql" && pg_ctlcluster 13 main stop
+
+# Modify the shared homeserver config with postgres support, certificate setup
+# and the disabling of rate-limiting
+COPY conf-workers/workers-shared.yaml /conf/workers/shared.yaml
+
+WORKDIR /data
+
+# Copy the caddy config
+COPY conf-workers/caddy.complement.json /root/caddy.json
+
+# Expose caddy's listener ports
+EXPOSE 8008 8448
+
+ENTRYPOINT \
+ # Replace the server name in the caddy config
+ sed -i "s/{{ server_name }}/${SERVER_NAME}/g" /root/caddy.json && \
+ # Start postgres
+ pg_ctlcluster 13 main start 2>&1 && \
+ # Start caddy
+ /root/caddy start --config /root/caddy.json 2>&1 && \
+ # Set the server name of the homeserver
+ SYNAPSE_SERVER_NAME=${SERVER_NAME} \
+ # No need to report stats here
+ SYNAPSE_REPORT_STATS=no \
+ # Set postgres authentication details which will be placed in the homeserver config file
+ POSTGRES_PASSWORD=somesecret POSTGRES_USER=postgres POSTGRES_HOST=localhost \
+ # Specify the workers to test with
+ SYNAPSE_WORKER_TYPES="\
+ event_persister, \
+ event_persister, \
+ background_worker, \
+ frontend_proxy, \
+ event_creator, \
+ user_dir, \
+ media_repository, \
+ federation_inbound, \
+ federation_reader, \
+ federation_sender, \
+ synchrotron, \
+ appservice, \
+ pusher" \
+ # Run the script that writes the necessary config files and starts supervisord, which in turn
+ # starts everything else
+ /configure_workers_and_start.py
+
+HEALTHCHECK --start-period=5s --interval=1s --timeout=1s \
+ CMD /bin/sh /healthcheck.sh
diff --git a/docker/complement/conf-workers/caddy.complement.json b/docker/complement/conf-workers/caddy.complement.json
new file mode 100644
index 0000000000..09e2136af2
--- /dev/null
+++ b/docker/complement/conf-workers/caddy.complement.json
@@ -0,0 +1,72 @@
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":8448"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "{{ server_name }}"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "reverse_proxy",
+ "upstreams": [
+ {
+ "dial": "localhost:8008"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "tls": {
+ "automation": {
+ "policies": [
+ {
+ "subjects": [
+ "{{ server_name }}"
+ ],
+ "issuers": [
+ {
+ "module": "internal"
+ }
+ ],
+ "on_demand": true
+ }
+ ]
+ }
+ },
+ "pki": {
+ "certificate_authorities": {
+ "local": {
+ "name": "Complement CA",
+ "root": {
+ "certificate": "/complement/ca/ca.crt",
+ "private_key": "/complement/ca/ca.key"
+ }
+ }
+ }
+ }
+ }
+ }
diff --git a/docker/complement/conf-workers/workers-shared.yaml b/docker/complement/conf-workers/workers-shared.yaml
new file mode 100644
index 0000000000..cdadb736f6
--- /dev/null
+++ b/docker/complement/conf-workers/workers-shared.yaml
@@ -0,0 +1,77 @@
+## Server ##
+report_stats: False
+trusted_key_servers: []
+enable_registration: true
+enable_registration_without_verification: true
+bcrypt_rounds: 4
+
+## Federation ##
+
+# disable verification of federation certificates
+#
+# TODO: Figure out why this is still needed even though we are making use of the custom CA
+federation_verify_certificates: false
+
+# trust certs signed by Complement's CA
+federation_custom_ca_list:
+- /complement/ca/ca.crt
+
+# unblacklist RFC1918 addresses
+federation_ip_range_blacklist: []
+
+# Disable server rate-limiting
+rc_federation:
+ window_size: 1000
+ sleep_limit: 10
+ sleep_delay: 500
+ reject_limit: 99999
+ concurrent: 3
+
+rc_message:
+ per_second: 9999
+ burst_count: 9999
+
+rc_registration:
+ per_second: 9999
+ burst_count: 9999
+
+rc_login:
+ address:
+ per_second: 9999
+ burst_count: 9999
+ account:
+ per_second: 9999
+ burst_count: 9999
+ failed_attempts:
+ per_second: 9999
+ burst_count: 9999
+
+rc_admin_redaction:
+ per_second: 9999
+ burst_count: 9999
+
+rc_joins:
+ local:
+ per_second: 9999
+ burst_count: 9999
+ remote:
+ per_second: 9999
+ burst_count: 9999
+
+federation_rr_transactions_per_room_per_second: 9999
+
+## Experimental Features ##
+
+experimental_features:
+ # Enable history backfilling support
+ msc2716_enabled: true
+ # Enable spaces support
+ spaces_enabled: true
+ # Enable jump to date endpoint
+ msc3030_enabled: true
+
+server_notices:
+ system_mxid_localpart: _server
+ system_mxid_display_name: "Server Alert"
+ system_mxid_avatar_url: ""
+ room_name: "Server Alert"
diff --git a/docker/complement/conf/homeserver.yaml b/docker/complement/conf/homeserver.yaml
new file mode 100644
index 0000000000..be53c4aa2e
--- /dev/null
+++ b/docker/complement/conf/homeserver.yaml
@@ -0,0 +1,122 @@
+## Server ##
+
+server_name: SERVER_NAME
+log_config: /conf/log_config.yaml
+report_stats: False
+signing_key_path: /conf/server.signing.key
+trusted_key_servers: []
+enable_registration: true
+enable_registration_without_verification: true
+
+## Listeners ##
+
+tls_certificate_path: /conf/server.tls.crt
+tls_private_key_path: /conf/server.tls.key
+bcrypt_rounds: 4
+registration_shared_secret: complement
+
+listeners:
+ - port: 8448
+ bind_addresses: ['::']
+ type: http
+ tls: true
+ resources:
+ - names: [federation]
+
+ - port: 8008
+ bind_addresses: ['::']
+ type: http
+
+ resources:
+ - names: [client]
+
+## Database ##
+
+database:
+ name: "sqlite3"
+ args:
+ # We avoid /data, as it is a volume and is not transferred when the container is committed,
+ # which is a fundamental necessity in complement.
+ database: "/conf/homeserver.db"
+
+## Federation ##
+
+
+# disable verification of federation certificates
+#
+# TODO: this is temporary; see
+# https://github.com/matrix-org/synapse/issues/11803
+federation_verify_certificates: false
+
+# trust certs signed by the complement CA
+federation_custom_ca_list:
+- /complement/ca/ca.crt
+
+# unblacklist RFC1918 addresses
+ip_range_blacklist: []
+
+# Disable server rate-limiting
+rc_federation:
+ window_size: 1000
+ sleep_limit: 10
+ sleep_delay: 500
+ reject_limit: 99999
+ concurrent: 3
+
+rc_message:
+ per_second: 9999
+ burst_count: 9999
+
+rc_registration:
+ per_second: 9999
+ burst_count: 9999
+
+rc_login:
+ address:
+ per_second: 9999
+ burst_count: 9999
+ account:
+ per_second: 9999
+ burst_count: 9999
+ failed_attempts:
+ per_second: 9999
+ burst_count: 9999
+
+rc_admin_redaction:
+ per_second: 9999
+ burst_count: 9999
+
+rc_joins:
+ local:
+ per_second: 9999
+ burst_count: 9999
+ remote:
+ per_second: 9999
+ burst_count: 9999
+
+federation_rr_transactions_per_room_per_second: 9999
+
+## API Configuration ##
+
+# A list of application service config files to use
+#
+app_service_config_files:
+AS_REGISTRATION_FILES
+
+## Experimental Features ##
+
+experimental_features:
+ # Enable spaces support
+ spaces_enabled: true
+ # Enable history backfilling support
+ msc2716_enabled: true
+ # server-side support for partial state in /send_join
+ msc3706_enabled: true
+ # Enable jump to date endpoint
+ msc3030_enabled: true
+
+server_notices:
+ system_mxid_localpart: _server
+ system_mxid_display_name: "Server Alert"
+ system_mxid_avatar_url: ""
+ room_name: "Server Alert"
diff --git a/docker/complement/conf/log_config.yaml b/docker/complement/conf/log_config.yaml
new file mode 100644
index 0000000000..c33fd6cd00
--- /dev/null
+++ b/docker/complement/conf/log_config.yaml
@@ -0,0 +1,24 @@
+version: 1
+
+formatters:
+ precise:
+ format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
+
+filters:
+ context:
+ (): synapse.logging.context.LoggingContextFilter
+ request: ""
+
+handlers:
+ console:
+ class: logging.StreamHandler
+ formatter: precise
+ filters: [context]
+ # log to stdout, for easier use with 'docker logs'
+ stream: 'ext://sys.stdout'
+
+root:
+ level: INFO
+ handlers: [console]
+
+disable_existing_loggers: false
diff --git a/docker/complement/conf/start.sh b/docker/complement/conf/start.sh
new file mode 100755
index 0000000000..5d8d0fe016
--- /dev/null
+++ b/docker/complement/conf/start.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+set -e
+
+sed -i "s/SERVER_NAME/${SERVER_NAME}/g" /conf/homeserver.yaml
+
+# Add the application service registration files to the homeserver.yaml config
+for filename in /complement/appservice/*.yaml; do
+ [ -f "$filename" ] || break
+
+ as_id=$(basename "$filename" .yaml)
+
+ # Insert the path to the registration file and the AS_REGISTRATION_FILES marker after
+ # so we can add the next application service in the next iteration of this for loop
+ sed -i "s/AS_REGISTRATION_FILES/ - \/complement\/appservice\/${as_id}.yaml\nAS_REGISTRATION_FILES/g" /conf/homeserver.yaml
+done
+# Remove the AS_REGISTRATION_FILES entry
+sed -i "s/AS_REGISTRATION_FILES//g" /conf/homeserver.yaml
+
+# generate an ssl key and cert for the server, signed by the complement CA
+openssl genrsa -out /conf/server.tls.key 2048
+
+openssl req -new -key /conf/server.tls.key -out /conf/server.tls.csr \
+ -subj "/CN=${SERVER_NAME}"
+openssl x509 -req -in /conf/server.tls.csr \
+ -CA /complement/ca/ca.crt -CAkey /complement/ca/ca.key -set_serial 1 \
+ -out /conf/server.tls.crt
+
+exec python -m synapse.app.homeserver -c /conf/homeserver.yaml "$@"
+
|