feat: add CI for docker

Docker and Compose support is provided through a standalone repo at
https://github.com/chatmail/docker, add reusable Docker build/test CI
jobs to staging workflows and a Docker docs stub pointing to the
chatmail/docker repository.

Requires CHATMAIL_DOCKER_DISPATCH_TOKEN secret in relay repo settings
(fine-grained PAT with contents:write on chatmail/docker).
This commit is contained in:
j4n
2026-04-14 16:52:04 +02:00
parent 2bdfecff72
commit 0808c9dc47
5 changed files with 191 additions and 3 deletions

125
.github/workflows/docker-deploy.yaml vendored Normal file
View File

@@ -0,0 +1,125 @@
name: Docker deploy
on:
workflow_call:
inputs:
staging_host:
required: true
type: string
description: 'SSH hostname (e.g. staging2.testrun.org)'
mail_domain:
required: true
type: string
description: 'MAIL_DOMAIN for docker compose'
zone_file:
required: true
type: string
description: 'Default zone file basename (e.g. staging.testrun.org-default.zone)'
jobs:
deploy-docker:
name: Docker deploy on ${{ inputs.staging_host }}
runs-on: ubuntu-latest
timeout-minutes: 20
environment:
name: ${{ inputs.staging_host }}
url: https://${{ inputs.staging_host }}/
concurrency: ${{ inputs.staging_host }}
env:
VPS: root@${{ inputs.staging_host }}
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup SSH
run: |
mkdir ~/.ssh
echo "${{ secrets.STAGING_SSH_KEY }}" >> ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan ${{ inputs.staging_host }} > ~/.ssh/known_hosts
# Reuse TCP connection for all subsequent ssh/scp calls
echo -e "Host ${{ inputs.staging_host }}\n ControlMaster auto\n ControlPath ~/.ssh/ctrl-%r@%h:%p\n ControlPersist 10m" >> ~/.ssh/config
- name: stop bare services, install Docker, prepare mounts
run: |
ssh $VPS bash -s <<'EOF'
systemctl stop postfix dovecot nginx opendkim unbound filtermail doveauth chatmail-metadata iroh-relay mtail fcgiwrap acmetool 2>/dev/null || true
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
mkdir -p /srv/chatmail/certs /srv/chatmail/dkim
cp -a /var/lib/acme/. /srv/chatmail/certs/ || true
cp -a /etc/dkimkeys/. /srv/chatmail/dkim/ || true
cp /etc/chatmail/chatmail.ini /srv/chatmail/chatmail.ini
EOF
- name: deploy with Docker
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
GHCR_IMAGE="ghcr.io/chatmail/docker:sha-${SHORT_SHA}"
rsync -avz --exclude='.git' --exclude='venv' --exclude='__pycache__' ./ $VPS:/srv/chatmail/relay/
echo "${{ secrets.GITHUB_TOKEN }}" | ssh $VPS "docker login ghcr.io -u ${{ github.actor }} --password-stdin && \
docker pull ${GHCR_IMAGE} && \
cd /srv/chatmail/relay && CHATMAIL_IMAGE=${GHCR_IMAGE} MAIL_DOMAIN=${{ inputs.mail_domain }} docker compose -f docker/docker-compose.yaml -f docker/docker-compose.ci.yaml up -d"
- name: wait for container healthy
run: |
ssh $VPS 'docker exec chatmail journalctl -f --no-pager' &
LOG_PID=$!
trap "kill $LOG_PID 2>/dev/null || true" EXIT
for i in $(seq 1 60); do
status=$(ssh $VPS 'docker inspect --format={{.State.Health.Status}} chatmail 2>/dev/null' || echo "missing")
echo " [$i/60] status=$status"
if [ "$status" = "healthy" ]; then
echo "Container is healthy."
exit 0
fi
if [ "$status" = "unhealthy" ]; then
echo "Container is unhealthy!"
break
fi
sleep 5
done
echo "Container did not become healthy."
kill $LOG_PID 2>/dev/null || true
ssh $VPS bash -s <<'EOF'
echo "--- failed units ---"
docker exec chatmail systemctl --failed --no-pager || true
echo "--- service logs ---"
docker exec chatmail journalctl -u dovecot -u postfix -u nginx -u unbound --no-pager -n 50 || true
echo "--- listening ports ---"
docker exec chatmail ss -tlnp || true
echo "--- chatmail.ini ---"
docker exec chatmail cat /etc/chatmail/chatmail.ini || true
EOF
exit 1
- name: show container state
run: |
ssh $VPS bash -s <<'EOF'
echo "--- listening ports ---"
docker exec chatmail ss -tlnp
echo "--- chatmail.ini ---"
docker exec chatmail cat /etc/chatmail/chatmail.ini
EOF
- name: Docker integration tests
run: ssh $VPS 'docker exec chatmail cmdeploy test --slow --ssh-host @local'
- name: Docker DNS
run: |
git checkout .github/workflows/${{ inputs.zone_file }}
ssh $VPS bash -s <<'EOF'
docker exec chatmail chown opendkim:opendkim -R /etc/dkimkeys
docker exec chatmail cmdeploy dns --ssh-host @local --zonefile /opt/chatmail/staging.zone --verbose
docker cp chatmail:/opt/chatmail/staging.zone /tmp/staging.zone
EOF
scp $VPS:/tmp/staging.zone staging-generated.zone
cat staging-generated.zone >> .github/workflows/${{ inputs.zone_file }}
cat .github/workflows/${{ inputs.zone_file }}
scp .github/workflows/${{ inputs.zone_file }} root@ns.testrun.org:/etc/nsd/${{ inputs.staging_host }}.zone
ssh root@ns.testrun.org "nsd-checkzone ${{ inputs.staging_host }} /etc/nsd/${{ inputs.staging_host }}.zone && systemctl reload nsd"
- name: Docker final DNS check
run: ssh $VPS 'docker exec chatmail cmdeploy dns -v --ssh-host @local'

View File

@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- j4n/docker-pr
pull_request: pull_request:
paths-ignore: paths-ignore:
- 'scripts/**' - 'scripts/**'
@@ -12,6 +13,11 @@ on:
- 'LICENSE' - 'LICENSE'
jobs: jobs:
trigger-docker-build:
if: github.event_name == 'push'
uses: ./.github/workflows/trigger-docker-build.yaml
secrets: inherit
deploy: deploy:
name: deploy on staging-ipv4.testrun.org, and run tests name: deploy on staging-ipv4.testrun.org, and run tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -22,6 +28,8 @@ jobs:
concurrency: staging-ipv4.testrun.org concurrency: staging-ipv4.testrun.org
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
submodules: true
- name: prepare SSH - name: prepare SSH
run: | run: |
@@ -63,7 +71,7 @@ jobs:
# download acme & dkim state from ns.testrun.org # download acme & dkim state from ns.testrun.org
rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4/acme acme-restore || true rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4/acme acme-restore || true
rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4/dkimkeys dkimkeys-restore || true rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4/dkimkeys dkimkeys-restore || true
# restore acme & dkim state to staging2.testrun.org # restore acme & dkim state to staging-ipv4.testrun.org
rsync -avz acme-restore/acme root@staging-ipv4.testrun.org:/var/lib/ || true rsync -avz acme-restore/acme root@staging-ipv4.testrun.org:/var/lib/ || true
rsync -avz dkimkeys-restore/dkimkeys root@staging-ipv4.testrun.org:/etc/ || true rsync -avz dkimkeys-restore/dkimkeys root@staging-ipv4.testrun.org:/etc/ || true
ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown root:root -R /var/lib/acme || true ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown root:root -R /var/lib/acme || true
@@ -102,3 +110,12 @@ jobs:
- name: cmdeploy dns - name: cmdeploy dns
run: ssh root@staging-ipv4.testrun.org "cd relay && scripts/cmdeploy dns -v --ssh-host localhost" run: ssh root@staging-ipv4.testrun.org "cd relay && scripts/cmdeploy dns -v --ssh-host localhost"
deploy-docker:
needs: [deploy, trigger-docker-build]
if: github.event_name == 'push'
uses: ./.github/workflows/docker-deploy.yaml
with:
staging_host: staging-ipv4.testrun.org
mail_domain: staging-ipv4.testrun.org
zone_file: staging-ipv4.testrun.org-default.zone
secrets: inherit

View File

@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- j4n/docker-pr
pull_request: pull_request:
paths-ignore: paths-ignore:
- 'scripts/**' - 'scripts/**'
@@ -12,6 +13,11 @@ on:
- 'LICENSE' - 'LICENSE'
jobs: jobs:
trigger-docker-build:
if: github.event_name == 'push'
uses: ./.github/workflows/trigger-docker-build.yaml
secrets: inherit
deploy: deploy:
name: deploy on staging2.testrun.org, and run tests name: deploy on staging2.testrun.org, and run tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -95,3 +101,12 @@ jobs:
- name: cmdeploy dns - name: cmdeploy dns
run: cmdeploy dns -v run: cmdeploy dns -v
deploy-docker:
needs: [deploy, trigger-docker-build]
if: github.event_name == 'push'
uses: ./.github/workflows/docker-deploy.yaml
with:
staging_host: staging2.testrun.org
mail_domain: staging2.testrun.org
zone_file: staging.testrun.org-default.zone
secrets: inherit

View File

@@ -0,0 +1,24 @@
name: Trigger Docker image build
on:
workflow_call:
jobs:
trigger-docker-build:
name: Trigger Docker image build
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
github-token: ${{ secrets.CHATMAIL_DOCKER_DISPATCH_TOKEN }}
script: |
await github.rest.repos.createDispatchEvent({
owner: 'chatmail',
repo: 'docker',
event_type: 'relay-updated',
client_payload: {
relay_ref: context.ref,
relay_sha: context.sha,
relay_sha_short: context.sha.slice(0, 7)
}
})

View File

@@ -98,6 +98,13 @@ steps. Please substitute it with your own domain.
configure at your DNS provider (it can take some time until they are configure at your DNS provider (it can take some time until they are
public). public).
Docker installation
-------------------
There is experimental support for running chatmail via Docker Compose.
See the `chatmail/docker README <https://github.com/chatmail/docker>`_
for full setup instructions.
Other helpful commands Other helpful commands
---------------------- ----------------------