- Shell 69.8%
- Python 27.6%
- Dockerfile 2.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo | ||
| .githooks | ||
| .openso | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| Dockerfile | ||
| entrypoint.traefik-certs-dumper.sh | ||
| LICENSE | ||
| mailcow-hook.sh | ||
| README.md | ||
traefik-certs-dumper
One-shot Traefik v3 ACME dump image. The entrypoint snapshots acme.json and writes PEM files. With MAILCOW_ENABLED=true it then copies the SMTP certificate to Mailcow over SSH, updates the existing TLSA record, and restarts postfix, dovecot, and nginx.
The customer stack lives in docker-templates at templates/traefik_certs-dumper. This repository is the image only.
Image
git.openso.de/openso/traefik-certs-dumper:main
docker-templates uses git.openso.de/openso/traefik-certs-dumper with no tag, so Compose pulls :latest. :latest tracks the same build as :main.
The process runs as uid/gid 1000 (certsdumper). The root filesystem is expected to be read-only. Writable paths are tmpfs plus the PEM destination.
Runtime
On start the entrypoint:
- Requires
ACME_FILENAME(one basename, charactersA-Za-z0-9._-). - Waits until
${ACME_DIR}/${ACME_FILENAME}exists and its JSON lists at least one certificate. - Polls forever. Each cycle copies the store to a tmpfs snapshot, skips the cycle when the snapshot digest is unchanged or another dump holds the lock, then runs
traefik-certs-dumper filewith--version v3,--domain-subdir,--crt-ext=.pem, and--key-ext=.pem. - When
MAILCOW_ENABLED=true, runs/usr/local/bin/mailcow-hook.shunder the same lock. The entrypoint exportsCERTS_DUMPER_SUPERVISOR_LOCK=1for that run.falseskips the hook. - After a successful dump, and a successful hook when Mailcow is enabled, writes
readyto${CERTS_DUMPER_RUNTIME_DIR}/ready. Compose healthchecks that file.
A failing Mailcow hook fails that cycle. The next poll retries. The image does not watch the live ACME file. MAILCOW_ENABLED accepts only true or false. Any other value exits before the poll loop.
With --domain-subdir the dump writes one directory per domain under ACME_DEST: <domain>/certificate.pem and <domain>/privatekey.pem. The Mailcow hook reads ${ACME_DEST}/${MAILCOW_SMTP_HOST}/.
Environment variables
The entrypoint reads these. Unset names use the default. ACME_FILENAME has no default.
| Variable | Default | Purpose |
|---|---|---|
ACME_FILENAME |
(required) | Basename of the ACME JSON file inside ACME_DIR, for example cloudflare-acme.json. |
ACME_DIR |
/data |
Directory that contains the ACME store. Mount it read-only. |
ACME_DEST |
/data/files |
Directory for dumped PEM files. Mount it read-write. |
CERTS_DUMPER_RUNTIME_DIR |
/run/certs-dumper |
Tmpfs for the ACME snapshot, the dump lock, and the ready marker. |
CERTS_DUMPER_POLL_SECONDS |
5 |
Seconds between poll cycles. |
MAILCOW_ENABLED |
false |
true copies the dumped SMTP certificate to Mailcow. false dumps PEM only. |
TZ is a normal container timezone. The image does not read it.
MAILCOW_ENABLED=false does not read SSH or the DNS token, so the container becomes ready after the first successful dump. true requires the variables and files below. The hook reads them only inside that run.
| Variable | Default | Purpose |
|---|---|---|
MAILCOW_SSH_HOST |
(required) | SSH host that receives the certificate. |
MAILCOW_SSH_USER |
certdeploy |
SSH user on that host. |
MAILCOW_DNS_ZONE |
(required) | Zone whose DNSSEC AD bit is checked before the TLSA write. |
MAILCOW_SMTP_HOST |
(required) | Hostname under ACME_DEST whose certificate.pem and privatekey.pem are copied. |
MAILCOW_PROJECT_PATH |
/opt/mailcow-dockerized |
Mailcow compose directory on the SSH host. |
The SSH private key is /run/secrets/TRAEFIK_CERTS_DUMPER_PASSWORD. The DNS token is /run/secrets/DNS_API_TOKEN (DNS_API_TOKEN_FILE overrides that path). Pinned host keys are /state/known_hosts (CERTS_DUMPER_SSH_KNOWN_HOSTS_FILE overrides that path). The hook does not write known_hosts and does not use accept-new. ACME_FILENAME must be cloudflare-acme.json or desec-acme.json. The hook updates one existing _25._tcp. TLSA record and restarts postfix-mailcow, dovecot-mailcow, and nginx-mailcow.
Compose
Use the template in docker-templates when this container sits next to Traefik. The shape below is that service: read-only ACME parent, writable PEM leaf, and a runtime tmpfs. CERTRESOLVER comes from the Traefik app .env and becomes ACME_FILENAME. Set MAILCOW_ENABLED to true only together with the SSH key, the DNS token, and the pinned known_hosts file.
services:
traefik_certs-dumper:
image: git.openso.de/openso/traefik-certs-dumper
user: "1000:1000"
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
init: true
tmpfs:
- /run:rw,noexec,nosuid,nodev,size=64m
- /run/certs-dumper:rw,noexec,nosuid,nodev,size=32m,mode=0700,uid=1000,gid=1000
- /tmp:rw,noexec,nosuid,nodev,size=128m
volumes:
- ./appdata/config/certs:/data:ro
- ./appdata/config/certs/files:/data/files:rw
environment:
TZ: Europe/Berlin
ACME_FILENAME: ${CERTRESOLVER:-cloudflare}-acme.json
MAILCOW_ENABLED: "false"
healthcheck:
test: ["CMD-SHELL", "test -f /run/certs-dumper/ready"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
Create ./appdata/config/certs/files on the host before start. Traefik must already write ${CERTRESOLVER}-acme.json into ./appdata/config/certs. The container stays in the healthcheck start period until that file contains a certificate and the first dump succeeds. With MAILCOW_ENABLED=true the Mailcow hook must succeed in that same cycle.
Remote copy (Mailcow SSH, known_hosts, DNS TLSA) is documented on the template. Enable that package there; do not add secrets to this image repository.
Build
A push to main builds and pushes :main and :latest. A tag v* builds and pushes :<tag> and :latest. The first commit on main is that push, so the first image is produced without a manual build.
The image compiles github.com/ldez/traefik-certs-dumper v2.11.4 with Go 1.26.10. golang.org/x/net v0.60.0, golang.org/x/text v0.42.0, and google.golang.org/grpc v1.83.2 replace the versions in that tag so the image scan can clear fixed HIGH findings. Alpine packages are pinned to the versions resolved for Alpine 3.24. The runtime image also includes openssh-client, jq, curl, openssl, and bind-tools for the baked Mailcow hook.