docker: make compose work with cgroups (v2), conversion scripts/docs

This commit is contained in:
j4n
2026-02-16 11:31:42 +01:00
parent f939c307f6
commit 645b60d293
4 changed files with 145 additions and 5 deletions

View File

@@ -3,12 +3,12 @@ services:
build: build:
context: ./ context: ./
dockerfile: docker/chatmail_relay.dockerfile dockerfile: docker/chatmail_relay.dockerfile
tags:
- chatmail-relay:latest
image: chatmail-relay:latest image: chatmail-relay:latest
restart: unless-stopped restart: unless-stopped
container_name: chatmail container_name: chatmail
cgroup: host # required for systemd # Required for systemd — use only one of the following:
cgroup: host # compose v2 only
# privileged: true # compose v1 (not tested)
tty: true # required for logs tty: true # required for logs
tmpfs: # required for systemd tmpfs: # required for systemd
- /tmp - /tmp

84
docker/cm_ini_to_env.py Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Convert a chatmail.ini to a Docker .env file.
Usage: python docker/cm_ini_to_env.py [chatmail.ini] [.env]
Reads the ini file, extracts all non-default key=value pairs,
and writes them as UPPER_CASE env vars suitable for docker-compose.
"""
import configparser
import sys
from pathlib import Path
# Keys that only make sense for bare-metal deploys or are handled
# separately by the Docker setup and should not appear in .env.
SKIP_KEYS = set()
# Keys that exist in .env but have a different name than the ini key.
# ini_key -> env_key
RENAMES = {}
def read_ini(path):
"""Return dict of key=value from [params] section."""
cp = configparser.ConfigParser()
cp.read(path)
if not cp.has_section("params"):
sys.exit(f"Error: {path} has no [params] section")
return dict(cp.items("params"))
def read_defaults():
"""Return dict of default values from the ini template."""
template = Path(__file__).resolve().parent.parent / "chatmaild/src/chatmaild/ini/chatmail.ini.f"
if not template.exists():
return {}
cp = configparser.ConfigParser()
cp.read(template)
if not cp.has_section("params"):
return {}
defaults = {}
for key, value in cp.items("params"):
# Template placeholders like {mail_domain} aren't real defaults.
if "{" not in value:
defaults[key] = value
return defaults
def ini_to_env(ini_path, only_non_default=True):
"""Yield (ENV_KEY, value) pairs from an ini file."""
params = read_ini(ini_path)
defaults = read_defaults() if only_non_default else {}
for key, value in sorted(params.items()):
if key in SKIP_KEYS:
continue
if only_non_default and key in defaults and value.strip() == defaults[key].strip():
continue
env_key = RENAMES.get(key, key.upper())
yield env_key, value.strip()
def main():
ini_path = sys.argv[1] if len(sys.argv) > 1 else "chatmail.ini"
env_path = sys.argv[2] if len(sys.argv) > 2 else None
if not Path(ini_path).exists():
sys.exit(f"Error: {ini_path} not found")
lines = []
for env_key, value in ini_to_env(ini_path):
lines.append(f'{env_key}="{value}"')
output = "\n".join(lines) + "\n"
if env_path:
Path(env_path).write_text(output)
print(f"Wrote {len(lines)} variables to {env_path}")
else:
print(output, end="")
if __name__ == "__main__":
main()

View File

@@ -67,7 +67,10 @@ git config --global --add safe.directory /opt/chatmail
if [ "$RECREATE_VENV" = true ]; then if [ "$RECREATE_VENV" = true ]; then
rm -rf venv rm -rf venv
fi fi
./scripts/initenv.sh # Skip venv creation if it already exists
if [ ! -x venv/bin/python ] || [ ! -x venv/bin/cmdeploy ]; then
./scripts/initenv.sh
fi
./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN || true ./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN || true
bash /update_ini.sh bash /update_ini.sh

View File

@@ -5,7 +5,13 @@
- 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). - 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 # Docker installation
This section provides instructions for installing Chatmail using docker-compose. This section provides instructions for installing Chatmail using Docker Compose.
**Note:** Docker Compose v2 is required (`docker compose`, not `docker-compose`) for its support of the `cgroup: host` option in `docker-compose.yaml` is only supported by Compose v2.
[see documentation](https://docs.docker.com/engine/install/debian/#install-using-the-repository)
```shell
apt install docker-ce docker-compose-plugin docker.io- docker-compose-
```
## Preliminary setup ## Preliminary setup
We use `chat.example.org` as the Chatmail domain in the following steps. We use `chat.example.org` as the Chatmail domain in the following steps.
@@ -75,6 +81,9 @@ docker compose up -d # start service
docker compose logs -f chatmail # view container logs, press CTRL+C to exit docker compose logs -f chatmail # view container logs, press CTRL+C to exit
``` ```
### venv creation
The first container start takes longer because it creates the cmdeploy Python virtualenv at `/opt/chatmail/venv` (persisted on the host via volume mount). Subsequent starts reuse the existing venv. Set `RECREATE_VENV=true` in `.env` to force a rebuild if needed.
6. After installation is complete, you can open `https://<your_domain_name>` in your browser. 6. After installation is complete, you can open `https://<your_domain_name>` in your browser.
## Using custom files ## Using custom files
@@ -114,6 +123,50 @@ docker compose down
docker compose up -d docker compose up -d
``` ```
## Migrating from a bare-metal install
If you have an existing bare-metal Chatmail installation and want to switch to Docker:
1. Stop all existing services:
```shell
systemctl stop postfix dovecot doveauth nginx opendkim unbound acmetool-redirector \
filtermail filtermail-incoming chatmail-turn iroh-relay chatmail-metadata \
lastlogin mtail
systemctl disable postfix dovecot doveauth nginx opendkim unbound acmetool-redirector \
filtermail filtermail-incoming chatmail-turn iroh-relay chatmail-metadata \
lastlogin mtail
```
2. Convert your existing `chatmail.ini` to the Docker `.env` format:
```shell
python3 docker/cm_ini_to_env.py /usr/local/lib/chatmaild/chatmail.ini .env
```
3. Copy persistent data into the `./data/` subdirectories:
```shell
mkdir -p data/chatmail-dkimkeys data/chatmail-acme data/chatmail
# DKIM keys
cp -a /etc/dkimkeys/* data/chatmail-dkimkeys/
# ACME certificates and account
rsync -a /var/lib/acme/ data/chatmail-acme/
# Mail data
rsync -a /home/ data/chatmail/
```
Alternatively, you can mount `/home/vmail` directly by changing the volume in `docker-compose.yaml`:
```yaml
- /home/vmail:/home/vmail
```
The three `./data/` subdirectories cover all persistent state. Everything else is regenerated by the `configure` and `activate` stages on container start.
## Forcing a full reinstall ## Forcing a full reinstall
The Docker image bakes the install stage (binary downloads, package setup, chatmaild venv) into the image at build time. On container start, only the `configure` and `activate` stages run by default. The Docker image bakes the install stage (binary downloads, package setup, chatmaild venv) into the image at build time. On container start, only the `configure` and `activate` stages run by default.