Skip to content

Commit 308dab3

Browse files
committed
docker: run install stage at build time, configure+activate at startup
Move the CMDEPLOY_STAGES=install execution into the Dockerfile these operations baked into the image layer. On container start, only configure and activate stages run by default. Users can override with CMDEPLOY_STAGES="install,configure,activate" to force a full reinstall without rebuilding the image. Also fixes CERTS_MONITORING_TIMEOUT typo in docker-compose.yaml (was "$CERTS MONITORING TIMEOUT" with spaces instead of underscores), and replaces the docker-commit workaround in docs with CMDEPLOY_STAGES documentation.
1 parent ab918ca commit 308dab3

5 files changed

Lines changed: 38 additions & 79 deletions

File tree

docker-compose.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ services:
2929
FORCE_REINIT_INI_FILE: $FORCE_REINIT_INI_FILE
3030
USE_FOREIGN_CERT_MANAGER: $USE_FOREIGN_CERT_MANAGER
3131
ENABLE_CERTS_MONITORING: $ENABLE_CERTS_MONITORING
32-
CERTS_MONITORING_TIMEOUT: $CERTS MONITORING TIMEOUT
32+
CERTS_MONITORING_TIMEOUT: $CERTS_MONITORING_TIMEOUT
3333
IS_DEVELOPMENT_INSTANCE: $IS_DEVELOPMENT_INSTANCE
34+
CMDEPLOY_STAGES: ${CMDEPLOY_STAGES:-}
3435
network_mode: "host"
3536
volumes:
3637
## system

docker/chatmail_relay.dockerfile

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,32 @@ RUN apt-get update && \
5454

5555
WORKDIR /opt/chatmail
5656

57+
# --- Build-time install stage ---
58+
# Bake the "install" deployer stage into the image; we can't use
59+
# scripts/initenv.sh because /opt/chatmail is empty at build time as
60+
# source arrives at runtime via volume mount., so we use a throwaway venv.
61+
# On container start only "configure,activate" stages run.
62+
COPY . /tmp/chatmail-src/
63+
WORKDIR /tmp/chatmail-src
64+
65+
# Dummy config — deploy_chatmail() needs a parseable ini to instantiate deployers
66+
RUN printf '[params]\nmail_domain = build.local\n' > /tmp/chatmail.ini
67+
68+
RUN python3 -m venv /tmp/build-venv && \
69+
/tmp/build-venv/bin/pip install --no-cache-dir \
70+
-e chatmaild -e cmdeploy
71+
72+
RUN CMDEPLOY_STAGES=install \
73+
CHATMAIL_INI=/tmp/chatmail.ini \
74+
CHATMAIL_DOCKER=True \
75+
/tmp/build-venv/bin/pyinfra @local \
76+
/tmp/chatmail-src/cmdeploy/src/cmdeploy/run.py -y
77+
78+
RUN rm -rf /tmp/chatmail-src /tmp/build-venv /tmp/chatmail.ini
79+
80+
WORKDIR /opt/chatmail
81+
# --- End build-time install stage ---
82+
5783
ARG SETUP_CHATMAIL_SERVICE_PATH=/lib/systemd/system/setup_chatmail.service
5884
COPY ./docker/files/setup_chatmail.service "$SETUP_CHATMAIL_SERVICE_PATH"
5985
RUN ln -sf "$SETUP_CHATMAIL_SERVICE_PATH" "/etc/systemd/system/multi-user.target.wants/setup_chatmail.service"
@@ -62,13 +88,6 @@ COPY --chmod=555 ./docker/files/setup_chatmail_docker.sh /setup_chatmail_docker.
6288
COPY --chmod=555 ./docker/files/update_ini.sh /update_ini.sh
6389
COPY --chmod=555 ./docker/files/entrypoint.sh /entrypoint.sh
6490

65-
## TODO: add git clone.
66-
## Problem: how correct save only required files inside container....
67-
# RUN git clone https://github.com/chatmail/relay.git -b master . \
68-
# && ./scripts/initenv.sh
69-
70-
# EXPOSE 443 25 587 143 993
71-
7291
VOLUME ["/sys/fs/cgroup", "/home"]
7392

7493
STOPSIGNAL SIGRTMIN+3
@@ -78,6 +97,3 @@ ENTRYPOINT ["/entrypoint.sh"]
7897
CMD [ "--default-standard-output=journal+console", \
7998
"--default-standard-error=journal+console" ]
8099

81-
## TODO: Add installation and configuration of chatmaild inside the Dockerfile.
82-
## This is required to ensure repeatable deployment.
83-
## In the current MVP, the chatmaild server is updated on every container restart.

docker/example.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ MAIL_DOMAIN="chat.example.com"
88
# ENABLE_CERTS_MONITORING="true"
99
# CERTS_MONITORING_TIMEOUT=10
1010
# IS_DEVELOPMENT_INSTANCE="True"
11+
# CMDEPLOY_STAGES - default: "configure,activate". Set to "install,configure,activate" to force full reinstall.

docker/files/setup_chatmail_docker.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fi
7070
./scripts/cmdeploy init --config "${INI_FILE}" $INI_CMD_ARGS $MAIL_DOMAIN || true
7171
bash /update_ini.sh
7272

73+
export CMDEPLOY_STAGES="${CMDEPLOY_STAGES:-configure,activate}"
7374
./scripts/cmdeploy run --ssh-host @docker
7475

7576
echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf

docs/DOCKER_INSTALLATION_EN.md

Lines changed: 8 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Known issues and limitations
22

3-
- 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 wasn’t designed for Docker. At the end of the documentation, there’s a [proposed solution](#locking-the-chatmail-version).
43
- Requires cgroups v2 configured in the system. Operation with cgroups v1 has not been tested.
54
- 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.
65
- 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).
@@ -59,6 +58,7 @@ cp ./docker/example.env .env
5958
- `PATH_TO_SSL` – Path to where the certificates are stored. (default: `/var/lib/acme/live/${MAIL_DOMAIN}`)
6059
- `ENABLE_CERTS_MONITORING` – Enable certificate monitoring if `USE_FOREIGN_CERT_MANAGER=true`. If certificates change, services will be automatically restarted. (default: `false`)
6160
- `CERTS_MONITORING_TIMEOUT` – Interval in seconds to check if certificates have changed. (default: `'60'`)
61+
- `CMDEPLOY_STAGES` – Deployment stages to run on container start. (default: `"configure,activate"`). Set to `"install,configure,activate"` to force a full reinstall.
6262

6363
You can also use any variables from the [ini configuration file](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/ini/chatmail.ini.f); they must be in uppercase.
6464

@@ -114,79 +114,19 @@ docker compose down
114114
docker compose up -d
115115
```
116116

117-
## Locking the Chatmail version
117+
## Forcing a full reinstall
118118

119-
> [!note]
120-
> These steps are optional and should only be done if you are not satisfied that the service is installed each time the container starts.
119+
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.
121120

122-
Since the current Docker version installs the Chatmail service every time the container starts, you can lock the container version after installation as follows:
123-
124-
1. Commit the current state of the configured container:
125-
126-
```shell
127-
docker container commit chatmail configured-chatmail:$(date +'%Y-%m-%d')
128-
docker image ls | grep configured-chatmail
129-
```
130-
131-
2. Change the entrypoint for the container in `docker-compose.yaml` to:
132-
133-
```yaml
134-
services:
135-
chatmail:
136-
image: <image name from step 1>
137-
volumes:
138-
...
139-
## custom resources
140-
- ./custom/setup_chatmail_docker.sh:/setup_chatmail_docker.sh
141-
```
142-
143-
3. Create the file `./custom/setup_chatmail_docker.sh` with the new configuration:
121+
To force a full reinstall (e.g., after updating the source), either rebuild the image:
144122

145123
```shell
146-
mkdir -p ./custom
147-
cat > ./custom/setup_chatmail_docker.sh << 'EOF'
148-
#!/bin/bash
149-
150-
set -eo pipefail
151-
152-
export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}"
153-
export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}"
154-
export PATH_TO_SSL="${PATH_TO_SSL:-/var/lib/acme/live/${MAIL_DOMAIN}}"
155-
156-
calculate_hash() {
157-
find "$PATH_TO_SSL" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}'
158-
}
159-
160-
monitor_certificates() {
161-
if [ "$ENABLE_CERTS_MONITORING" != "true" ]; then
162-
echo "Certs monitoring disabled."
163-
exit 0
164-
fi
165-
166-
current_hash=$(calculate_hash)
167-
previous_hash=$current_hash
168-
169-
while true; do
170-
current_hash=$(calculate_hash)
171-
if [[ "$current_hash" != "$previous_hash" ]]; then
172-
# TODO: add an option to restart at a specific time interval
173-
echo "[INFO] Certificate's folder hash was changed, reloading nginx, dovecot and postfix services."
174-
systemctl reload nginx.service
175-
systemctl reload dovecot.service
176-
systemctl reload postfix.service
177-
previous_hash=$current_hash
178-
fi
179-
sleep $CERTS_MONITORING_TIMEOUT
180-
done
181-
}
182-
183-
monitor_certificates &
184-
EOF
124+
docker compose build chatmail
125+
docker compose up -d
185126
```
186127

187-
4. Restart the service:
128+
Or override the stages at runtime without rebuilding:
188129

189130
```shell
190-
docker compose down
191-
docker compose up -d
131+
CMDEPLOY_STAGES="install,configure,activate" docker compose up -d
192132
```

0 commit comments

Comments
 (0)