logologo
  • Guide
  • Development
  • Reference
    • Configuration Reference
    • Admin Command Reference
    • Server Reference
  • Community
  • Security
Matrix
Forgejo
    Continuwuity
    Configuration
    Deploying
    Generic
    Docker
    Debian
    Fedora
    NixOS
    Arch Linux
    Kubernetes
    FreeBSD
    TURN
    Appservices
    Maintenance
    Troubleshooting
    Development
    Development Guide
    Contributing
    Code Style Guide
    Testing
    Hot Reloading
    Reference
    Configuration Reference
    Admin Command Reference
    Server Reference
    Continuwuity Community Guidelines
    Security Policy for Continuwuity

    Last Updated: 11/15/2025, 8:25:54 PM

    Previous pageGenericNext pageDebian

    #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

    OCI images for Continuwuity are available in the registries listed below.

    RegistryImageNotes
    Forgejo Registryforgejo.ellis.link/continuwuation/continuwuity:latestLatest tagged image.
    Forgejo Registryforgejo.ellis.link/continuwuation/continuwuity:mainMain branch image.

    Use

    docker image pull $LINK

    to pull it to your machine.

    #Run

    When you have the image, you can simply run it with

    docker run -d -p 8448:6167 \
        -v db:/var/lib/continuwuity/ \
        -e CONTINUWUITY_SERVER_NAME="your.server.name" \
        -e CONTINUWUITY_ALLOW_REGISTRATION=false \
        --name continuwuity $LINK

    or you can use Docker Compose.

    The -d flag lets the container run in detached mode. You may supply an optional continuwuity.toml config file, the example config can be found here. You can pass in different env vars to change config values on the fly. You can even configure Continuwuity completely by using env vars. For an overview of possible values, please take a look at the docker-compose.yml file.

    If you just want to test Continuwuity for a short time, you can use the --rm flag, which cleans up everything related to your container after you stop it.

    #Docker-compose

    If the docker run command is not suitable for you or your setup, you can also use one of the provided docker-compose files.

    Depending on your proxy setup, you can use one of the following files:

    #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
          - /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" # 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"
    
          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
    

    #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

    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
    

    When picking the Traefik-related compose file, rename it to docker-compose.yml, and rename the override file to docker-compose.override.yml. Edit the latter with the values you want for your server.

    When picking the caddy-docker-proxy compose file, it's important to first create the caddy network before spinning up the containers:

    docker network create caddy

    After that, you can rename it to docker-compose.yml and spin up the containers!

    Additional info about deploying Continuwuity can be found here.

    #Build

    Official Continuwuity images are built using Docker Buildx and the Dockerfile found at docker/Dockerfile. This approach uses common Docker tooling and enables efficient multi-platform builds.

    The resulting images are widely compatible with Docker and other container runtimes like Podman or containerd.

    The images do not contain a shell. They contain only the Continuwuity binary, required libraries, TLS certificates, and metadata.

    Click to view the Dockerfile

    You can also view the Dockerfile on Forgejo.

    ARG RUST_VERSION=1
    ARG DEBIAN_VERSION=bookworm
    
    FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
    FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS base
    FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS toolchain
    
    # Prevent deletion of apt cache
    RUN rm -f /etc/apt/apt.conf.d/docker-clean
    
    # Match Rustc version as close as possible
    # rustc -vV
    ARG LLVM_VERSION=20
    # ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
    
    # Install repo tools
    # Line one: compiler tools
    # Line two: curl, for downloading binaries
    # Line three: for xx-verify
    RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
        --mount=type=cache,target=/var/lib/apt,sharing=locked \
        apt-get update && apt-get install -y \
        pkg-config make jq \
        curl git software-properties-common \
        file
    
    # LLVM packages
    RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
        --mount=type=cache,target=/var/lib/apt,sharing=locked \
        curl https://apt.llvm.org/llvm.sh > llvm.sh && \
        chmod +x llvm.sh && \
        ./llvm.sh ${LLVM_VERSION} && \
        rm llvm.sh
    
    # Create symlinks for LLVM tools
    RUN <<EOF
        set -o xtrace
        # clang
        ln -s /usr/bin/clang-${LLVM_VERSION} /usr/bin/clang
        ln -s "/usr/bin/clang++-${LLVM_VERSION}" "/usr/bin/clang++"
        # lld
        ln -s /usr/bin/ld64.lld-${LLVM_VERSION} /usr/bin/ld64.lld
        ln -s /usr/bin/ld.lld-${LLVM_VERSION} /usr/bin/ld.lld
        ln -s /usr/bin/lld-${LLVM_VERSION} /usr/bin/lld
        ln -s /usr/bin/lld-link-${LLVM_VERSION} /usr/bin/lld-link
        ln -s /usr/bin/wasm-ld-${LLVM_VERSION} /usr/bin/wasm-ld
    EOF
    
    # Developer tool versions
    # renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
    ENV BINSTALL_VERSION=1.16.2
    # renovate: datasource=github-releases depName=psastras/sbom-rs
    ENV CARGO_SBOM_VERSION=0.9.1
    # renovate: datasource=crate depName=lddtree
    ENV LDDTREE_VERSION=0.3.7
    # renovate: datasource=crate depName=timelord-cli
    ENV TIMELORD_VERSION=3.0.1
    
    # Install unpackaged tools
    RUN <<EOF
        set -o xtrace
        curl --retry 5 -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
        cargo binstall --no-confirm cargo-sbom --version $CARGO_SBOM_VERSION
        cargo binstall --no-confirm lddtree --version $LDDTREE_VERSION
        cargo binstall --no-confirm timelord-cli --version $TIMELORD_VERSION
    EOF
    
    # Set up xx (cross-compilation scripts)
    COPY --from=xx / /
    ARG TARGETPLATFORM
    
    # Install libraries linked by the binary
    # xx-* are xx-specific meta-packages
    RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
        --mount=type=cache,target=/var/lib/apt,sharing=locked \
        xx-apt-get install -y \
        xx-c-essentials xx-cxx-essentials pkg-config \
        liburing-dev
    
    # Set up Rust toolchain
    WORKDIR /app
    COPY ./rust-toolchain.toml .
    RUN rustc --version \
        && xx-cargo --setup-target-triple
    
    # Build binary
    # Configure incremental compilation based on build context
    ARG CARGO_INCREMENTAL=0
    RUN echo "CARGO_INCREMENTAL=${CARGO_INCREMENTAL}" >> /etc/environment
    
    # Configure pkg-config
    RUN <<EOF
        set -o xtrace
        if command -v "$(xx-info)-pkg-config" >/dev/null 2>/dev/null; then
            echo "PKG_CONFIG_LIBDIR=/usr/lib/$(xx-info)/pkgconfig" >> /etc/environment
            echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment
        fi
        echo "PKG_CONFIG_ALLOW_CROSS=true" >> /etc/environment
    EOF
    
    # Configure cc to use clang version
    RUN <<EOF
        set -o xtrace
        echo "CC=clang" >> /etc/environment
        echo "CXX=clang++" >> /etc/environment
    EOF
    
    # Cross-language LTO
    RUN <<EOF
        set -o xtrace
        echo "CFLAGS=-flto" >> /etc/environment
        echo "CXXFLAGS=-flto" >> /etc/environment
        # Linker is set to target-compatible clang by xx
        echo "RUSTFLAGS='-Clinker-plugin-lto -Clink-arg=-fuse-ld=lld'" >> /etc/environment
    EOF
    
    # Apply CPU-specific optimizations if TARGET_CPU is provided
    ARG TARGET_CPU
    
    RUN <<EOF
        set -o allexport
        set -o xtrace
        . /etc/environment
        if [ -n "${TARGET_CPU}" ]; then
            echo "CFLAGS='${CFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
            echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
            echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment
        fi
    EOF
    
    # Prepare output directories
    RUN mkdir /out
    
    FROM toolchain AS builder
    
    
    # Get source
    COPY . .
    
    # Restore timestamps from timelord cache if available
    RUN --mount=type=cache,target=/timelord/ \
        echo "Restoring timestamps from timelord cache"; \
        timelord sync --source-dir /app --cache-dir /timelord;
    
    ARG TARGETPLATFORM
    
    # Verify environment configuration
    RUN xx-cargo --print-target-triple
    
    # Conduwuit version info
    ARG GIT_COMMIT_HASH
    ARG GIT_COMMIT_HASH_SHORT
    ARG GIT_REMOTE_URL
    ARG GIT_REMOTE_COMMIT_URL
    ARG CONDUWUIT_VERSION_EXTRA
    ARG CONTINUWUITY_VERSION_EXTRA
    ENV GIT_COMMIT_HASH=$GIT_COMMIT_HASH
    ENV GIT_COMMIT_HASH_SHORT=$GIT_COMMIT_HASH_SHORT
    ENV GIT_REMOTE_URL=$GIT_REMOTE_URL
    ENV GIT_REMOTE_COMMIT_URL=$GIT_REMOTE_COMMIT_URL
    ENV CONDUWUIT_VERSION_EXTRA=$CONDUWUIT_VERSION_EXTRA
    ENV CONTINUWUITY_VERSION_EXTRA=$CONTINUWUITY_VERSION_EXTRA
    
    ARG RUST_PROFILE=release
    
    # Build the binary
    RUN --mount=type=cache,target=/usr/local/cargo/registry \
        --mount=type=cache,target=/usr/local/cargo/git/db \
        --mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
        bash <<'EOF'
        set -o allexport
        set -o xtrace
        . /etc/environment
        TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
                jq -r ".target_directory"))
        mkdir /out/sbin
        PACKAGE=conduwuit
        xx-cargo build --locked --profile ${RUST_PROFILE} \
            -p $PACKAGE;
        BINARIES=($(cargo metadata --no-deps --format-version 1 | \
            jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
        for BINARY in "${BINARIES[@]}"; do
            echo $BINARY
            xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
            cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
        done
    EOF
    
    # Generate Software Bill of Materials (SBOM)
    RUN --mount=type=cache,target=/usr/local/cargo/registry \
        --mount=type=cache,target=/usr/local/cargo/git/db \
        bash <<'EOF'
        set -o xtrace
        mkdir /out/sbom
        typeset -A PACKAGES
        for BINARY in /out/sbin/*; do
            BINARY_BASE=$(basename ${BINARY})
            package=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.targets[] | select( .kind | map(. == \"bin\") | any ) | .name == \"$BINARY_BASE\") | .name")
            if [ -z "$package" ]; then
                continue
            fi
            PACKAGES[$package]=1
        done
        for PACKAGE in $(echo ${!PACKAGES[@]}); do
            echo $PACKAGE
            cargo sbom --cargo-package $PACKAGE > /out/sbom/$PACKAGE.spdx.json
        done
    EOF
    
    # Extract dynamically linked dependencies
    RUN <<'DEPS_EOF'
        set -o xtrace
        mkdir /out/libs /out/libs-root
    
        # Process each binary
        for BINARY in /out/sbin/*; do
            if lddtree_output=$(lddtree "$BINARY" 2>/dev/null) && [ -n "$lddtree_output" ]; then
                echo "$lddtree_output" | awk '{print $(NF-0) " " $1}' | sort -u -k 1,1 | \
                    awk '{dest = ($2 ~ /^\//) ? "/out/libs-root" $2 : "/out/libs/" $2; print "install -D " $1 " " dest}' | \
                    while read cmd; do eval "$cmd"; done
            fi
        done
    
        # Show what will be copied to runtime
        echo "=== Libraries being copied to runtime image:"
        find /out/libs* -type f 2>/dev/null | sort || echo "No libraries found"
    DEPS_EOF
    
    FROM ubuntu:latest AS prepper
    
    # Create layer structure
    RUN mkdir -p /layer1/etc/ssl/certs \
                 /layer2/usr/lib \
                 /layer3/sbin /layer3/sbom
    
    # Copy SSL certs and root-path libraries to layer1 (ultra-stable)
    COPY --from=base /etc/ssl/certs /layer1/etc/ssl/certs
    COPY --from=builder /out/libs-root/ /layer1/
    
    # Copy application libraries to layer2 (semi-stable)
    COPY --from=builder /out/libs/ /layer2/usr/lib/
    
    # Copy binaries and SBOM to layer3 (volatile)
    COPY --from=builder /out/sbin/ /layer3/sbin/
    COPY --from=builder /out/sbom/ /layer3/sbom/
    
    # Fix permissions after copying
    RUN chmod -R 755 /layer1 /layer2 /layer3
    
    FROM scratch
    
    WORKDIR /
    
    # Copy ultra-stable layer (SSL certs, system libraries)
    COPY --from=prepper /layer1/ /
    
    # Copy semi-stable layer (application libraries)
    COPY --from=prepper /layer2/ /
    
    # Copy volatile layer (binaries, SBOM)
    COPY --from=prepper /layer3/ /
    
    # Inform linker where to find libraries
    ENV LD_LIBRARY_PATH=/usr/lib
    
    # Continuwuity default port
    EXPOSE 8008
    
    CMD ["/sbin/conduwuit"]
    

    To build an image locally using Docker Buildx, you can typically run a command like:

    # Build for the current platform and load into the local Docker daemon
    docker buildx build --load --tag continuwuity:latest -f docker/Dockerfile .
    
    # Example: Build for specific platforms and push to a registry.
    # docker buildx build --platform linux/amd64,linux/arm64 --tag registry.io/org/continuwuity:latest -f docker/Dockerfile . --push
    
    # Example: Build binary optimized for the current CPU
    # docker buildx build --load --tag continuwuity:latest --build-arg TARGET_CPU=native -f docker/Dockerfile .

    Refer to the Docker Buildx documentation for more advanced build options.

    #Run

    If you have already built the image or want to use one from the registries, you can start the container and everything else in the compose file in detached mode with:

    docker compose up -d

    Note: Don't forget to modify and adjust the compose file to your needs.

    #Use Traefik as Proxy

    As a container user, you probably know about Traefik. It is an easy-to-use reverse proxy for making containerized apps and services available through the web. With the Traefik-related docker-compose files provided above, it is equally easy to deploy and use Continuwuity, with a small caveat. If you have already looked at the files, you should have seen the well-known service, which is the small caveat. Traefik is simply a proxy and load balancer and cannot serve any kind of content. For Continuwuity to federate, we need to either expose ports 443 and 8448 or serve two endpoints: .well-known/matrix/client and .well-known/matrix/server.

    With the service well-known, we use a single nginx container that serves those two files.

    Alternatively, you can use Continuwuity's built-in delegation file capability. Set up the delegation files in the configuration file, and then proxy paths under /.well-known/matrix to continuwuity. For example, the label traefik.http.routers.continuwuity.rule=(Host(`matrix.ellis.link`) || (Host(`ellis.link`) && PathPrefix(`/.well-known/matrix`))) does this for the domain ellis.link.

    #Voice communication

    See the TURN page.