Continuwuity for Docker

Docker

To run Continuwuity with Docker, you can either build the image yourself or pull it from a registry.

Use a registry

Available OCI images:

Example:

docker image pull forgejo.ellis.link/continuwuation/continuwuity:main-maxperf

Mirrors

Images are mirrored to multiple locations automatically, on a schedule:

  • ghcr.io/continuwuity/continuwuity
  • docker.io/jadedblueeyes/continuwuity
  • registry.gitlab.com/continuwuity/continuwuity
  • git.nexy7574.co.uk/mirrored/continuwuity (releases only, no main)

Quick Run

Get a working Continuwuity server with an admin user in four steps:

Prerequisites

Continuwuity requires HTTPS for Matrix federation. You'll need:

  • A domain name pointing to your server
  • A reverse proxy with SSL/TLS certificates (Traefik, Caddy, nginx, etc.)

See Docker Compose for complete examples.

Environment Variables

  • CONTINUWUITY_SERVER_NAME - Your Matrix server's domain name
  • CONTINUWUITY_DATABASE_PATH - Where to store your database (must match the volume mount)
  • CONTINUWUITY_ADDRESS - Bind address (use 0.0.0.0 to listen on all interfaces)
  • CONTINUWUITY_ALLOW_REGISTRATION - Set to false to disable registration, or use with CONTINUWUITY_REGISTRATION_TOKEN to require a token (see reference for details)

See the Environment Variables Reference for more configuration options.

1. Pull the image

docker pull forgejo.ellis.link/continuwuation/continuwuity:latest

2. Start the server with initial admin user

docker run -d \
  -p 6167:6167 \
  -v continuwuity_db:/var/lib/continuwuity \
  -e CONTINUWUITY_SERVER_NAME="matrix.example.com" \
  -e CONTINUWUITY_DATABASE_PATH="/var/lib/continuwuity" \
  -e CONTINUWUITY_ADDRESS="0.0.0.0" \
  -e CONTINUWUITY_ALLOW_REGISTRATION="false" \
  --name continuwuity \
  forgejo.ellis.link/continuwuation/continuwuity:latest \
  --execute "users create-user admin"

Replace matrix.example.com with your actual server name and admin with your preferred username.

3. Get your admin password

docker logs continuwuity 2>&1 | grep "Created user"

You'll see output like:

Created user with user_id: @admin:matrix.example.com and password: `[auto-generated-password]`

4. Configure your reverse proxy

Configure your reverse proxy to forward HTTPS traffic to Continuwuity. See Docker Compose for examples.

Once configured, log in with any Matrix client using @admin:matrix.example.com and the generated password. You'll automatically be invited to the admin room where you can manage your server.

Docker Compose

Docker Compose is the recommended deployment method. These examples include reverse proxy configurations for Matrix federation.

Matrix Federation Requirements

For Matrix federation to work, you need to serve .well-known/matrix/client and .well-known/matrix/server endpoints. You can achieve this either by:

  1. Using a well-known service - The compose files below include an nginx container to serve these files
  2. Using Continuwuity's built-in delegation (easier for Traefik) - Configure delegation files in your config, then proxy /.well-known/matrix/* to Continuwuity

Traefik example using built-in delegation:

labels:
  traefik.http.routers.continuwuity.rule: >-
    (Host(`matrix.example.com`) ||
     (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))

This routes your Matrix domain and well-known paths to Continuwuity.

Creating Your First Admin User

Add the --execute command to create an admin user on first startup. In your compose file, add under the continuwuity service:

services:
    continuwuity:
        image: forgejo.ellis.link/continuwuation/continuwuity:latest
        command: --execute "users create-user admin"
        # ... rest of configuration

Then retrieve the auto-generated password:

docker compose logs continuwuity | grep "Created user"

Choose Your Reverse Proxy

Select the compose file that matches your setup:

DNS Performance

Docker's default DNS resolver can cause performance issues with Matrix federation. If you experience slow federation or DNS timeouts, you may need to use your host's DNS resolver instead. Add this volume mount to the continuwuity service:

volumes:
  - /etc/resolv.conf:/etc/resolv.conf:ro

See Troubleshooting - DNS Issues for more details and alternative solutions.

For existing Traefik setup
docker-compose.for-traefik.yml
# Continuwuity - Behind Traefik Reverse Proxy

services:
  homeserver:
    ### If you already built the continuwuity image with 'docker build' or want to use the Docker Hub image,
    ### then you are ready to go.
    image: forgejo.ellis.link/continuwuation/continuwuity:latest
    restart: unless-stopped
    volumes:
      - db:/var/lib/continuwuity
      #- ./continuwuity.toml:/etc/continuwuity.toml
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
      - "traefik.http.routers.continuwuity.entrypoints=websecure" # your HTTPS entry point
      - "traefik.http.routers.continuwuity.tls=true"
      - "traefik.http.routers.continuwuity.service=continuwuity"
      - "traefik.http.services.continuwuity.loadbalancer.server.port=6167"
      # possibly, depending on your config:
      # - "traefik.http.routers.continuwuity.tls.certresolver=letsencrypt"
    environment:
      CONTINUWUITY_SERVER_NAME: your.server.name.example # EDIT THIS
      CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
      CONTINUWUITY_PORT: 6167 # should match the loadbalancer traefik label
      CONTINUWUITY_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
      CONTINUWUITY_ALLOW_REGISTRATION: 'true'
      CONTINUWUITY_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
      #CONTINUWUITY_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
      CONTINUWUITY_ALLOW_FEDERATION: 'true'
      CONTINUWUITY_ALLOW_CHECK_FOR_UPDATES: 'true'
      CONTINUWUITY_TRUSTED_SERVERS: '["matrix.org"]'
      #CONTINUWUITY_LOG: warn,state_res=warn
      CONTINUWUITY_ADDRESS: 0.0.0.0
      #CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above

      # We need some way to serve the client and server .well-known json. The simplest way is via the CONTINUWUITY_WELL_KNOWN
      # variable / config option, there are multiple ways to do this, e.g. in the continuwuity.toml file, and in a separate
      # see the override file for more information about delegation
      CONTINUWUITY_WELL_KNOWN: |
        {
        client=https://your.server.name.example,
        server=your.server.name.example:443
        }
    #cpuset: "0-4" # Uncomment to limit to specific CPU cores
    ulimits: # Continuwuity uses quite a few file descriptors, and on some systems it defaults to 1024, so you can tell docker to increase it
      nofile:
        soft: 1048567
        hard: 1048567

    ### Uncomment if you want to use your own Element-Web App.
    ### Note: You need to provide a config.json for Element and you also need a second
    ###       Domain or Subdomain for the communication between Element and Continuwuity
    ### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
    # element-web:
    #     image: vectorim/element-web:latest
    #     restart: unless-stopped
    #     volumes:
    #         - ./element_config.json:/app/config.json
    #     networks:
    #         - proxy
    #     depends_on:
    #         - homeserver

volumes:
  db:

networks:
  # This is the network Traefik listens to, if your network has a different
  # name, don't forget to change it here and in the docker-compose.override.yml
  proxy:
    external: true

# vim: ts=2:sw=2:expandtab
With Traefik included
docker-compose.with-traefik.yml
# Continuwuity - Behind Traefik Reverse Proxy

services:
  homeserver:
    ### If you already built the Continuwuity image with 'docker build' or want to use the Docker Hub image,
    ### then you are ready to go.
    image: forgejo.ellis.link/continuwuation/continuwuity:latest
    restart: unless-stopped
    volumes:
      - db:/var/lib/continuwuity
      - /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
      #- ./continuwuity.toml:/etc/continuwuity.toml
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
      - "traefik.http.routers.continuwuity.entrypoints=websecure"
      - "traefik.http.routers.continuwuity.tls.certresolver=letsencrypt"
      - "traefik.http.services.continuwuity.loadbalancer.server.port=6167"
      # Uncomment and adjust the following if you want to use middleware
      # - "traefik.http.routers.continuwuity.middlewares=secureHeaders@file"
    environment:
      CONTINUWUITY_SERVER_NAME: your.server.name.example # EDIT THIS
      CONTINUWUITY_TRUSTED_SERVERS: '["matrix.org"]'
      CONTINUWUITY_ALLOW_REGISTRATION: 'false' # After setting a secure registration token, you can enable this
      CONTINUWUITY_REGISTRATION_TOKEN: "" # This is a token you can use to register on the server
      #CONTINUWUITY_REGISTRATION_TOKEN_FILE: "" # Alternatively you can configure a path to a token file to read
      CONTINUWUITY_ADDRESS: 0.0.0.0
      CONTINUWUITY_PORT: 6167 # you need to match this with the traefik load balancer label if you're want to change it
      CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
      #CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
      ### Uncomment and change values as desired, note that Continuwuity has plenty of config options, so you should check out the example example config too
      # Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
      # CONTINUWUITY_LOG: info  # default is: "warn,state_res=warn"
      # CONTINUWUITY_ALLOW_ENCRYPTION: 'true'
      # CONTINUWUITY_ALLOW_FEDERATION: 'true'
      # CONTINUWUITY_ALLOW_CHECK_FOR_UPDATES: 'true'
      # CONTINUWUITY_ALLOW_INCOMING_PRESENCE: true
      # CONTINUWUITY_ALLOW_OUTGOING_PRESENCE: true
      # CONTINUWUITY_ALLOW_LOCAL_PRESENCE: true
      # CONTINUWUITY_WORKERS: 10
      # CONTINUWUITY_MAX_REQUEST_SIZE: 20000000  # in bytes, ~20 MB
      # CONTINUWUITY_NEW_USER_DISPLAYNAME_SUFFIX = "🏳<200d>⚧"

      # We need some way to serve the client and server .well-known json. The simplest way is via the CONTINUWUITY_WELL_KNOWN
      # variable / config option, there are multiple ways to do this, e.g. in the continuwuity.toml file, and in a separate
      # reverse proxy, but since you do not have a reverse proxy and following this guide, this example is included
      CONTINUWUITY_WELL_KNOWN: |
        {
          client=https://your.server.name.example,
          server=your.server.name.example:443
        }
    #cpuset: "0-4" # Uncomment to limit to specific CPU cores
    ulimits: # Continuwuity uses quite a few file descriptors, and on some systems it defaults to 1024, so you can tell docker to increase it
      nofile:
        soft: 1048567
        hard: 1048567

    ### Uncomment if you want to use your own Element-Web App.
    ### Note: You need to provide a config.json for Element and you also need a second
    ###       Domain or Subdomain for the communication between Element and Continuwuity
    ### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
    # element-web:
    #     image: vectorim/element-web:latest
    #     restart: unless-stopped
    #     volumes:
    #         - ./element_config.json:/app/config.json
    #     networks:
    #         - proxy
    #     depends_on:
    #         - homeserver

  traefik:
    image: "traefik:latest"
    container_name: "traefik"
    restart: "unless-stopped"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:z"
      - "acme:/etc/traefik/acme"
      #- "./traefik_config:/etc/traefik:z"
    labels:
      - "traefik.enable=true"

      # middleware redirect
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      # global redirect to https
      - "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.redirs.entrypoints=web"
      - "traefik.http.routers.redirs.middlewares=redirect-to-https"

    configs:
      - source: dynamic.yml
        target: /etc/traefik/dynamic.yml

    environment:
      TRAEFIK_LOG_LEVEL: DEBUG
      TRAEFIK_ENTRYPOINTS_WEB: true
      TRAEFIK_ENTRYPOINTS_WEB_ADDRESS: ":80"
      TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_TO: websecure

      TRAEFIK_ENTRYPOINTS_WEBSECURE: true
      TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS: ":443"
      TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_TLS_CERTRESOLVER: letsencrypt
      #TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_MIDDLEWARES: secureHeaders@file # if you want to enabled STS

      TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT: true
      TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL: # Set this to the email you want to receive certificate expiration emails for
      TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_KEYTYPE: EC384
      TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE: true
      TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT: web
      TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE: "/etc/traefik/acme/acme.json"

      # Since Traefik 3.6.3, paths with certain "encoded characters" are now blocked by default; we need a couple, or else things *will* break
      TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSLASH: true
      TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDHASH: true

      TRAEFIK_PROVIDERS_DOCKER: true
      TRAEFIK_PROVIDERS_DOCKER_ENDPOINT: "unix:///var/run/docker.sock"
      TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: false

      TRAEFIK_PROVIDERS_FILE: true
      TRAEFIK_PROVIDERS_FILE_FILENAME: "/etc/traefik/dynamic.yml"

configs:
  dynamic.yml:
    content: |
      # Optionally set STS headers, like in https://hstspreload.org
      # http:
      #   middlewares:
      #     secureHeaders:
      #       headers:
      #         forceSTSHeader: true
      #         stsIncludeSubdomains: true
      #         stsPreload: true
      #         stsSeconds: 31536000
      tls:
        options:
          default:
            cipherSuites:
              - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
              - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
              - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
              - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
              - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
              - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
            minVersion: VersionTLS12

volumes:
    db:
    acme:

networks:
    proxy:

# vim: ts=2:sw=2:expandtab
With Caddy Docker Proxy
docker-compose.with-caddy.yml

Replace all example.com placeholders with your own domain.

services:
    caddy:
    # This compose file uses caddy-docker-proxy as the reverse proxy for Continuwuity!
    # For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
        image: lucaslorentz/caddy-docker-proxy:ci-alpine
        ports:
            - 80:80
            - 443:443
        environment:
            - CADDY_INGRESS_NETWORKS=caddy
        networks:
            - caddy
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - ./data:/data
        restart: unless-stopped
        labels:
            caddy: example.com
            caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
            caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}

    homeserver:
        ### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
        ### then you are ready to go.
        image: forgejo.ellis.link/continuwuation/continuwuity:latest
        restart: unless-stopped
        volumes:
            - db:/var/lib/continuwuity
            - /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
            #- ./continuwuity.toml:/etc/continuwuity.toml
        environment:
            CONTINUWUITY_SERVER_NAME: example.com # EDIT THIS
            CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
            CONTINUWUITY_PORT: 6167
            CONTINUWUITY_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
            CONTINUWUITY_ALLOW_REGISTRATION: 'true'
            CONTINUWUITY_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
            #CONTINUWUITY_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
            CONTINUWUITY_ALLOW_FEDERATION: 'true'
            CONTINUWUITY_ALLOW_CHECK_FOR_UPDATES: 'true'
            CONTINUWUITY_TRUSTED_SERVERS: '["matrix.org"]'
            #CONTINUWUITY_LOG: warn,state_res=warn
            CONTINUWUITY_ADDRESS: 0.0.0.0
            #CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
        networks:
            - caddy
        labels:
            caddy: matrix.example.com
            caddy.reverse_proxy: "{{upstreams 6167}}"

volumes:
    db:

networks:
    caddy:
        external: true

If you don't already have a network for Caddy to monitor, create one first:

docker network create caddy
For other reverse proxies
docker-compose.yml
# Continuwuity

services:
    homeserver:
        ### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
        ### then you are ready to go.
        image: forgejo.ellis.link/continuwuation/continuwuity:latest
        restart: unless-stopped
        ports:
            - 8448:6167
        volumes:
            - db:/var/lib/continuwuity
            #- ./continuwuity.toml:/etc/continuwuity.toml
        environment:
            CONTINUWUITY_SERVER_NAME: your.server.name # EDIT THIS
            CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
            CONTINUWUITY_PORT: 6167
            CONTINUWUITY_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
            CONTINUWUITY_ALLOW_REGISTRATION: 'true'
            CONTINUWUITY_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
            #CONTINUWUITY_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
            CONTINUWUITY_ALLOW_FEDERATION: 'true'
            CONTINUWUITY_ALLOW_CHECK_FOR_UPDATES: 'true'
            CONTINUWUITY_TRUSTED_SERVERS: '["matrix.org"]'
            #CONTINUWUITY_LOG: warn,state_res=warn
            CONTINUWUITY_ADDRESS: 0.0.0.0
            #CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
    #
    ### Uncomment if you want to use your own Element-Web App.
    ### Note: You need to provide a config.json for Element and you also need a second
    ###       Domain or Subdomain for the communication between Element and Continuwuity
    ### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
    # element-web:
    #     image: vectorim/element-web:latest
    #     restart: unless-stopped
    #     ports:
    #         - 8009:80
    #     volumes:
    #         - ./element_config.json:/app/config.json
    #     depends_on:
    #         - homeserver

volumes:
    db:
Override file for customisation
docker-compose.override.yml
# Continuwuity - Traefik Reverse Proxy Labels

services:
  homeserver:
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"  # Change this to the name of your Traefik docker proxy network

      - "traefik.http.routers.to-continuwuity.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)"  # Change to the address on which Continuwuity is hosted
      - "traefik.http.routers.to-continuwuity.tls=true"
      - "traefik.http.routers.to-continuwuity.tls.certresolver=letsencrypt"
      - "traefik.http.routers.to-continuwuity.middlewares=cors-headers@docker"
      - "traefik.http.services.to_continuwuity.loadbalancer.server.port=6167"

      - "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
      - "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
      - "traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS"

      # If you want to have your account on <DOMAIN>, but host Continuwuity on a subdomain,
      # you can let it only handle the well known file on that domain instead
      #- "traefik.http.routers.to-matrix-wellknown.rule=Host(`<DOMAIN>`) && PathPrefix(`/.well-known/matrix`)"
      #- "traefik.http.routers.to-matrix-wellknown.tls=true"
      #- "traefik.http.routers.to-matrix-wellknown.tls.certresolver=letsencrypt"
      #- "traefik.http.routers.to-matrix-wellknown.middlewares=cors-headers@docker"

  ### Uncomment this if you uncommented Element-Web App in the docker-compose.yml
  # element-web:
  #     labels:
  #         - "traefik.enable=true"
  #         - "traefik.docker.network=proxy"  # Change this to the name of your Traefik docker proxy network

  #         - "traefik.http.routers.to-element-web.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)"  # Change to the address on which Element-Web is hosted
  #         - "traefik.http.routers.to-element-web.tls=true"
  #         - "traefik.http.routers.to-element-web.tls.certresolver=letsencrypt"

# vim: ts=2:sw=2:expandtab

Starting Your Server

  1. Choose your compose file and rename it to docker-compose.yml
  2. If using the override file, rename it to docker-compose.override.yml and edit your values
  3. Start the server:
docker compose up -d

See the generic deployment guide for more deployment options.

Building Custom Images

For information on building your own Continuwuity Docker images, see the Building Docker Images section in the development documentation.

Voice communication

See the Calls page.