Files
relay/docs/DOCKER_INSTALLATION_EN.md
Keonik1 3826de8c60 Add installation via docker compose (MVP 1)
- Add markdown tabs blocks
- Fix [Issue 604](https://github.com/chatmail/relay/issues/604)
- Add `--skip-dns-check` argument to `cmdeploy run` command
- Add `--force` argument to `cmdeploy init` command
- Add startup for `fcgiwrap.service`
- Add extended check when installing `unbound.service`
- Add configuration parameters (`is_development_instance`, `use_foreign_cert_manager`, `acme_email`, `change_kernel_settings`, `fs_inotify_max_user_instances_and_watchers`)
2025-08-09 15:55:37 +03:00

10 KiB
Raw Permalink Blame History

Known issues and limitations

  • Installation using acmetool (docker-compose-default.yaml) may NOT work. In this case, use installation via traefik (docker-compose-traefik.yaml). Personally, during my tests, I encountered the error could not install DNS challenge, no hooks succeeded;, which I was unable to fix.
  • Chatmail will be reinstalled every time the container is started (longer the first time, faster on subsequent starts). This is how the original installer works because it wasnt designed for Docker. At the end of the documentation, theres a proposed solution.
  • Requires cgroups v2 configured in the system. Operation with cgroups v1 has not been tested.
  • Yes, of course, using systemd inside a container is a hack, and it would be better to split it into several services, but since this is an MVP, it turned out to be easier to do it this way initially than to rewrite the entire deployment system.
  • The Docker image is only suitable for amd64. If you need to run it on a different architecture, try modifying the Dockerfile (specifically the part responsible for installing dovecot).

Docker installation

This section provides instructions for installing Chatmail using docker-compose.

Preliminary setup

We use chat.example.org as the Chatmail domain in the following steps. Please substitute it with your own domain.

  1. Setup the initial DNS records. The following is an example in the familiar BIND zone file format with a TTL of 1 hour (3600 seconds). Please substitute your domain and IP addresses.

     chat.example.com. 3600 IN A 198.51.100.5
     chat.example.com. 3600 IN AAAA 2001:db8::5
     www.chat.example.com. 3600 IN CNAME chat.example.com.
     mta-sts.chat.example.com. 3600 IN CNAME chat.example.com.
    
  2. clone the repository on your server.

     git clone https://github.com/chatmail/relay
     cd relay
    

Installation

When installing via Docker, there are several options:

  • Use the built-in nginx and acmetool in Chatmail container to host the chat and manage certificates.
  • Use third-party tools for certificate management.

For the third-party certificate manager example, traefik will be used, but you can use whatever is more convenient for you.

  1. Copy the file ./docker/docker-compose-default.yaml or ./docker/docker-compose-traefik.yaml and rename it to docker-compose.yaml. This is necessary because docker-compose.yaml is in .gitignore and wont cause conflicts when updating the git repository.
cp ./docker/docker-compose-default.yaml docker-compose.yaml
## or
# cp ./docker/docker-compose-traefik.yaml docker-compose.yaml
  1. Copy ./docker/example.env and rename it to .env. This file stores variables used in docker-compose.yaml. Required only when installing with traefik; if using the default setup, you can skip this step.
cp ./docker/example.env .env
  1. Configure kernel parameters because they cannot be changed inside the container, specifically fs.inotify.max_user_instances and fs.inotify.max_user_watches. Run the following:
echo "fs.inotify.max_user_instances=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf
echo "fs.inotify.max_user_watches=65536" | sudo tee -a /etc/sysctl.d/99-inotify.conf
sudo sysctl --system
  1. Configure container environment variables. Below is the list of variables used during deployment:
  • MAIL_DOMAIN The domain name of the future server. (required)
  • DEBUG_COMMANDS_ENABLED Run debug commands before installation. (default: false)
  • FORCE_REINIT_INI_FILE Recreate the ini configuration file on startup. (default: false)
  • USE_FOREIGN_CERT_MANAGER Use a third-party certificate manager. (default: false)
  • INI_FILE Path to the ini configuration file. (default: ./chatmail.ini)
  • PATH_TO_SSL_CONTAINER Path to where the certificates are stored. (default: /var/lib/acme/live/${MAIL_DOMAIN})
  • ENABLE_CERTS_MONITORING Enable certificate monitoring if USE_FOREIGN_CERT_MANAGER=true. If certificates change, services will be automatically restarted. (default: false)
  • CERTS_MONITORING_TIMEOUT Interval in seconds to check if certificates have changed. (default: '60')

You can also use any variables from the ini configuration file; they must be in uppercase.

Mandatory variables for deployment via Docker:

  • CHANGE_KERNEL_SETTINGS Change kernel settings (fs.inotify.max_user_instances and fs.inotify.max_user_watches) on startup. Changing kernel settings inside the container is not possible! (default: False)
  1. Configure environment variables in the .env file. These variables are used in the docker-compose.yaml file to pass repeated values.

  2. Build the Docker image:

docker compose build chatmail
Additional steps for configuring with traefik

Note

If you are using the default installation without traefik skip these steps and go to step 7 (running docker compose).

Before starting traefik, configuration files must be prepared; otherwise, it will not start correctly.

First, run these commands in the console, replacing their values with the correct ones:

export YOUR_EMAIL=your_email@gmail.com
mkdir -p "./data/traefik"
cd "./data/traefik"
  1. Create a traefik configuration file:
cat > config.yaml << EOF
log:
  level: TRACE

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          permanent: true
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: /dynamic/conf
    watch: true

serverstransport:
  insecureskipverify: true

certificatesResolvers:
  letsEncrypt:
    acme:
      email: $YOUR_EMAIL
      storage: /acme.json
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      tlschallenge: true
      httpChallenge:
        entryPoint: web
EOF
  1. Create a post-hook script:
cat > post-hook.sh << 'EOF'
CERTS_DIR=${CERTS_DIR:-"/data/letsencrypt/certs"}

for dir in "$CERTS_DIR"/*/; do
    cd "$dir"
    if [ -f "certificate.crt" ]; then
        ln -sf certificate.crt fullchain
    fi
    if [ -f "privatekey.key" ]; then
        ln -sf privatekey.key privkey
    fi
    cd -
done
EOF
  1. Create the acme.json file:
touch acme.json
sudo chown 0:0 ./acme.json # required
sudo chmod 600 ./acme.json # required
  1. Create insecure config:
mkdir dynamic-configs
cat > ./dynamic-configs/insecure.yaml << 'EOF'
http:
  serversTransports:
    insecure:
      insecureSkipVerify: true
EOF
cd ../..
  1. Start docker compose and wait for the installation to finish:
docker compose up -d # start service
docker compose logs -f chatmail # view container logs, press CTRL+C to exit
  1. After installation is complete, you can open https://<your_domain_name> in your browser.

Using custom files

When using Docker, you can apply modified configuration files to make the installation more personalized. This is usually needed for the www/src section so that the Chatmail landing page is customized to your taste, but it can be used for any other cases as well.

To replace files correctly:

  1. Create the ./custom directory. It is in .gitignore, so it wont cause conflicts when updating.
mkdir -p ./custom
  1. Modify the required file. For example, index.md:
mkdir -p ./custom/www/src
nano ./custom/www/src/index.md
  1. In docker-compose.yaml, add the file mount in the volumes section:
services:
  chatmail:
    volumes:
      ...
      ## custom resources
      - ./custom/www/src/index.md:/opt/chatmail/www/src/index.md
  1. Restart the service:
docker compose down
docker compose up -d

Locking the Chatmail version

Note

These steps are optional and should only be done if you are not satisfied that the service is installed each time the container starts.

Since the current Docker version installs the Chatmail service every time the container starts, you can lock the container version after installation as follows:

  1. Commit the current state of the configured container:
docker container commit chatmail configured-chatmail:$(date +'%Y-%m-%d')
docker image ls | grep configured-chatmail
  1. Change the entrypoint for the container in docker-compose.yaml to:
services:
  chatmail:
    image: <image name from step 1>
    volumes:
      ...
      ## custom resources
      - ./custom/setup_chatmail_docker.sh:/setup_chatmail_docker.sh
  1. Create the file ./custom/setup_chatmail_docker.sh with the new configuration:
mkdir -p ./custom
cat > ./custom/setup_chatmail_docker.sh << 'EOF'
#!/bin/bash

set -eo pipefail

export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}"
export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}"
export PATH_TO_SSL_CONTAINER="${PATH_TO_SSL_CONTAINER:-/var/lib/acme/live/${MAIL_DOMAIN}}"

calculate_hash() {
    find "$PATH_TO_SSL_CONTAINER" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}'
}

monitor_certificates() {
    if [ "$ENABLE_CERTS_MONITORING" != "true" ]; then
        echo "Certs monitoring disabled."
        exit 0
    fi

    current_hash=$(calculate_hash)
    previous_hash=$current_hash

    while true; do
        current_hash=$(calculate_hash)
        if [[ "$current_hash" != "$previous_hash" ]]; then
        # TODO: add an option to restart at a specific time interval 
            echo "[INFO] Certificate's folder hash was changed, restarting nginx, dovecot and postfix services."
            systemctl restart nginx.service
            systemctl reload dovecot.service
            systemctl reload postfix.service
            previous_hash=$current_hash
        fi
        sleep $CERTS_MONITORING_TIMEOUT
    done
}

monitor_certificates &
EOF
  1. Restart the service:
docker compose down
docker compose up -d