Skip to content

Commit f840f5c

Browse files
committed
docker: skip redundant cmdeploy run on container restart
Replace the old IMAGE_VERSION_FILE/RUNNING_VERSION_FILE mechanism with a single deploy fingerprint (image_version:sha256(chatmail.ini)) stored at /etc/chatmail/.deploy-fingerprint. On restart, if the fingerprint matches the last successful deploy, skip cmdeploy run entirely. The fingerprint lives on the container's writable layer. On fresh containers, setting CMDEPLOY_STAGES non-empty in env forces a deploy run regardless of fingerprint. Also narrow the /home volume mount to /home/vmail.
1 parent a643585 commit f840f5c

File tree

4 files changed

+107
-27
lines changed

4 files changed

+107
-27
lines changed

DOCKER.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Docker Setup
2+
3+
## Image (`docker/chatmail_relay.dockerfile`)
4+
5+
Base: `jrei/systemd-debian:12` (systemd-enabled Debian).
6+
7+
**Build-time:** `COPY . /opt/chatmail/`, create venv at `/opt/cmdeploy/`, run `CMDEPLOY_STAGES=install` via pyinfra.
8+
Build needs `CHATMAIL_NOSYSCTL=True` and `CHATMAIL_NOPORTCHECK=True` since sysctl/ports aren't available during build.
9+
Dovecot is installed by the pyinfra install stage (not duplicated in Dockerfile).
10+
11+
**Runtime:** systemd entrypoint, `setup_chatmail.service` runs `setup_chatmail_docker.sh` after boot.
12+
13+
## Startup flow (`docker/files/setup_chatmail_docker.sh`)
14+
15+
1. Validate `MAIL_DOMAIN` is set
16+
2. Generate DKIM keys if missing (`opendkim-genkey`)
17+
3. Generate `chatmail.ini` if missing (`cmdeploy init`) — skipped when volume-mounted
18+
4. Compute deploy fingerprint (`image_version:sha256(chatmail.ini)`) — if it matches the last successful deploy, skip `cmdeploy run` entirely (plain restart with no changes)
19+
5. Run `cmdeploy run --config $CHATMAIL_INI --ssh-host @local` (configure+activate stages, or install too if image version changed)
20+
6. Certificate monitoring handled by `chatmail-certmon.timer` (systemd timer, every 60s)
21+
22+
## Compose (`docker-compose.yaml`)
23+
24+
- `cgroup: host` — required for systemd (compose v2 only)
25+
- `network_mode: host` — no port mapping needed (see security note in compose file)
26+
- `.env` file: copy from `docker/example.env`, set `MAIL_DOMAIN`
27+
28+
### Volumes
29+
30+
| Mount | Purpose |
31+
|-------|---------|
32+
| `/sys/fs/cgroup` | systemd (required) |
33+
| `./data/chatmail:/home/vmail` | Mail storage (mailboxes + passdb) |
34+
| `./data/chatmail-dkimkeys:/etc/dkimkeys` | DKIM keys (persisted) |
35+
| `./data/chatmail-acme:/var/lib/acme` | ACME certs |
36+
| `./chatmail.ini:/etc/chatmail/chatmail.ini` | Optional: mount custom config |
37+
38+
### Environment vars (runtime)
39+
40+
| Variable | Required | Default |
41+
|----------|----------|---------|
42+
| `MAIL_DOMAIN` | Yes ||
43+
| `CMDEPLOY_STAGES` | No | `configure,activate` |
44+
| `USE_FOREIGN_CERT_MANAGER` | No ||
45+
46+
## Config modes
47+
48+
**Simple:** Set `MAIL_DOMAIN` in `.env`. Container generates `chatmail.ini` with defaults on first start.
49+
50+
**Advanced:** Extract ini from running container (`docker cp chatmail:/etc/chatmail/chatmail.ini ./`), edit, mount back.
51+
52+
## Commands inside container
53+
54+
```shell
55+
docker compose exec chatmail cmdeploy dns --ssh-host @local
56+
docker compose exec chatmail cmdeploy status --ssh-host @local
57+
```
58+
59+
`CHATMAIL_INI` env var is set in the image, so `--config` is not needed.
60+
61+
## .dockerignore
62+
63+
Excludes: `data/`, `venv/`, `__pycache__`, `*.pyc`, `*.orig`, `*.ini`, `.pytest_cache`, `.env`, `.git`, `.github/`, `docs/`, `tests/`, `*.md` (except `www/**/*.md`).
64+
65+
`.git` is excluded to slim the build context. The git hash is passed via `GIT_HASH` build arg instead:
66+
```shell
67+
GIT_HASH=$(git rev-parse HEAD) docker compose build chatmail
68+
```
69+
CI sets this automatically via `github.sha`. Without it, the image version defaults to `"unknown"`.
70+
71+
## Known issues
72+
73+
- Kernel params (`fs.inotify.*`) must be set on the host, not in the container
74+
- amd64-only — no multi-arch support (deployer supports arm64 but Dockerfile doesn't)

docker-compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ services:
4141
## system (required)
4242
- /sys/fs/cgroup:/sys/fs/cgroup:rw
4343
## data (defaults — override in docker-compose.override.yaml)
44-
- chatmail-mail:/home
44+
- chatmail-data:/home/vmail
4545
- chatmail-dkimkeys:/etc/dkimkeys
4646
- chatmail-acme:/var/lib/acme
4747

4848
volumes:
49-
chatmail-mail:
49+
chatmail-data:
5050
chatmail-dkimkeys:
5151
chatmail-acme:

docker/docker-compose.override.yaml.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ services:
1111
## Data paths — bind-mount to host directories for easy access/backup.
1212
## Uncomment and adjust paths as needed. These override the named
1313
## volumes in the base docker-compose.yaml.
14-
# - ./data/chatmail:/home
14+
# - ./data/chatmail:/home/vmail
1515
# - ./data/chatmail-dkimkeys:/etc/dkimkeys
1616
# - ./data/chatmail-acme:/var/lib/acme
1717

docker/files/setup_chatmail_docker.sh

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,46 @@ fi
1818
# Fix ownership for bind-mounted keys (host opendkim UID may differ from container)
1919
chown -R opendkim:opendkim /etc/dkimkeys
2020

21+
# Journald: forward to console for docker logs
22+
grep -q '^ForwardToConsole=yes' /etc/systemd/journald.conf \
23+
|| echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf
24+
systemctl restart systemd-journald
25+
2126
# Create chatmail.ini (skips if file already exists, e.g. volume-mounted)
2227
mkdir -p "$(dirname "$CHATMAIL_INI")"
2328
if [ ! -f "$CHATMAIL_INI" ]; then
2429
$CMDEPLOY init --config "$CHATMAIL_INI" "$MAIL_DOMAIN"
2530
fi
2631

27-
# Auto-detect image upgrades: if the image version changed since last run,
28-
# include the install stage so new packages/binaries are picked up.
32+
# --- Deploy fingerprint: skip cmdeploy run if nothing changed ---
2933
IMAGE_VERSION_FILE="/etc/chatmail-image-version"
30-
RUNNING_VERSION_FILE="/home/.chatmail-running-version"
31-
CMDEPLOY_STAGES="${CMDEPLOY_STAGES:-configure,activate}"
32-
if [ -f "$IMAGE_VERSION_FILE" ]; then
33-
image_ver=$(cat "$IMAGE_VERSION_FILE")
34-
running_ver=""
35-
if [ -f "$RUNNING_VERSION_FILE" ]; then
36-
running_ver=$(cat "$RUNNING_VERSION_FILE")
37-
fi
38-
if [ "$image_ver" != "$running_ver" ]; then
39-
echo "[INFO] Image version changed ($running_ver -> $image_ver), adding install stage."
34+
FINGERPRINT_FILE="/etc/chatmail/.deploy-fingerprint"
35+
image_ver="none"
36+
[ -f "$IMAGE_VERSION_FILE" ] && image_ver=$(cat "$IMAGE_VERSION_FILE")
37+
config_hash=$(sha256sum "$CHATMAIL_INI" | cut -c1-16)
38+
current_fp="${image_ver}:${config_hash}"
39+
40+
# CMDEPLOY_STAGES non-empty in env = operator override -> always run.
41+
# Otherwise, if fingerprint matches the last successful deploy, skip.
42+
if [ -z "${CMDEPLOY_STAGES:-}" ] \
43+
&& [ -f "$FINGERPRINT_FILE" ] \
44+
&& [ "$(cat "$FINGERPRINT_FILE")" = "$current_fp" ]; then
45+
echo "[INFO] No changes detected ($current_fp), skipping deploy."
46+
else
47+
# Determine stages: default is configure,activate.
48+
# If image version changed since last deploy, prepend install stage.
49+
CMDEPLOY_STAGES="${CMDEPLOY_STAGES:-configure,activate}"
50+
prev_fp=""
51+
[ -f "$FINGERPRINT_FILE" ] && prev_fp=$(cat "$FINGERPRINT_FILE")
52+
prev_image_ver="${prev_fp%%:*}"
53+
if [ "$image_ver" != "$prev_image_ver" ]; then
54+
echo "[INFO] Image version changed ($prev_image_ver -> $image_ver), adding install stage."
4055
case "$CMDEPLOY_STAGES" in
4156
*install*) ;; # already includes install
4257
*) CMDEPLOY_STAGES="install,$CMDEPLOY_STAGES" ;;
4358
esac
4459
fi
60+
export CMDEPLOY_STAGES
61+
$CMDEPLOY run --config "$CHATMAIL_INI" --ssh-host @local
62+
echo "$current_fp" > "$FINGERPRINT_FILE"
4563
fi
46-
export CMDEPLOY_STAGES
47-
$CMDEPLOY run --config "$CHATMAIL_INI" --ssh-host @local
48-
49-
# Record successful version after deploy
50-
if [ -f "$IMAGE_VERSION_FILE" ]; then
51-
cp "$IMAGE_VERSION_FILE" "$RUNNING_VERSION_FILE"
52-
fi
53-
54-
# Journald: forward to console for docker logs (idempotent)
55-
grep -q '^ForwardToConsole=yes' /etc/systemd/journald.conf \
56-
|| echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf
57-
systemctl restart systemd-journald

0 commit comments

Comments
 (0)