No description
Find a file
paspo cbecde1708
All checks were successful
Container Publish / on-success-skip (push) Has been skipped
Container Publish / build-image (amd64) (push) Successful in 40s
Container Publish / build-image (arm64) (push) Successful in 42s
Container Publish / update docker manifest (push) Successful in 8s
ignore go vendor
2026-04-12 13:03:35 +02:00
.forgejo/workflows initial import 2026-04-12 13:01:54 +02:00
src initial import 2026-04-12 13:01:54 +02:00
.cursorignore ignore go vendor 2026-04-12 13:03:35 +02:00
.gitignore ignore go vendor 2026-04-12 13:03:35 +02:00
Dockerfile initial import 2026-04-12 13:01:54 +02:00
LICENSE initial import 2026-04-12 13:01:54 +02:00
README.md initial import 2026-04-12 13:01:54 +02:00

traefik-certdump

Lightweight service that reads Traefiks acme.json under the letsencrypt resolver block, extracts certificate and key as PEM files, and writes them to disk on a schedule. It exposes a local HTTP /health endpoint (for Docker HEALTHCHECK or similar).

The final image is based on scratch and contains only the statically linked Go binary.

How it works

  1. At startup, the YAML file from --config-file (if any) and environment variables are loaded once. On each cycle, only Traefiks acme.json is re-read when the file has changed or had not yet been loaded successfully.
  2. The expected JSON shape is {"letsencrypt":{"certificates":[...]}}. Each entry has domain (main, sans), certificate, and key as base64 (as Traefik stores them). Only this path is read: other top-level resolver keys are ignored.
  3. For each configured destination, a certificate whose domain.main exactly matches the requested domain is used (same string as in your config). SANs are not used for matching.
  4. Chain and key are written atomically (temp file + rename), creating directories if missing.
  5. When temporal validity checks are enabled, a certificate that is not yet valid or already expired causes the export cycle to fail and the health endpoint returns 503 until the situation is resolved.
  6. GET http://127.0.0.1:8080/health returns 200 with JSON {"status":"healthy","message":"...","lastUpdate":"RFC3339"} when the last cycle succeeded; otherwise 503 with "status":"unhealthy".

The first read/write cycle runs immediately after startup; later cycles use POLL_SECONDS / poll_seconds.

Docker

The image default command is /traefik-certdump. The Dockerfile defines a HEALTHCHECK that runs /traefik-certdump health, i.e. an HTTP request to http://127.0.0.1:8080/health like the main process serves.

For full docker run examples see Examples.

Configuration

Environment variables (single domain)

Without --config-file, one destination is defined with ACME_DOMAIN, ACME_CERT_OUT, and ACME_KEY_OUT (absolute PEM paths). JSON_PATH is still required unless the same path is set as json_path in YAML used with --config-file.

If any of the three ACME_* variables is set, all three must be set.

YAML file (--config-file)

The file is read once at startup; only acme.json is observed on later intervals. For multiple domains, use YAML, for example:

docker run --rm \
  -v /path/to/config.yaml:/config.yaml:ro \
  -v ... \
  docker.asperti.com/paspo/traefik-certdump:latest \
  --config-file /config.yaml

Schema (all top-level keys are optional; you need at least one destination in the file and/or via env):

YAML key Type Description
json_path string Path to acme.json.
poll_seconds int Polling interval in seconds (positive; defaults to 3600 if omitted).
disable_expiry_check bool Skips temporal validity checks on the certificate (defaults to false if omitted).
domains list Each item: domain, cert_path, key_path.

Precedence: for json_path, poll_seconds, and disable_expiry_check, if the matching environment variable is set and non-empty, it overrides YAML. Destinations from the file and from the env triplet are merged; if the same domain appears twice, the last registration wins (the env triplet is applied after YAML entries).

If POLL_SECONDS is unset and poll_seconds is missing from YAML (or not positive), 3600 is used.

Examples

Single domain (env only)

docker run --rm \
  -v /var/lib/docker/volumes/traefik_acme/_data/acme.json:/acme.json:ro \
  -v /certs/out:/out \
  -e JSON_PATH=/acme.json \
  -e ACME_DOMAIN=example.com \
  -e ACME_CERT_OUT=/out/fullchain.pem \
  -e ACME_KEY_OUT=/out/privkey.pem \
  docker.asperti.com/paspo/traefik-certdump:latest

Multiple domains (YAML)

Example config.yaml:

json_path: /acme.json
poll_seconds: 3600
disable_expiry_check: false
domains:
  - domain: www.example.com
    cert_path: /certs/www/fullchain.pem
    key_path: /certs/www/privkey.pem
  - domain: api.example.com
    cert_path: /certs/api/fullchain.pem
    key_path: /certs/api/privkey.pem
docker run --rm \
  -v /var/lib/docker/volumes/traefik_acme/_data/acme.json:/acme.json:ro \
  -v /path/config.yaml:/config.yaml:ro \
  -v /certs/www:/certs/www \
  -v /certs/api:/certs/api \
  docker.asperti.com/paspo/traefik-certdump:latest \
  --config-file /config.yaml

Build from source

cd src
go build -o traefik-certdump ./cmd/traefik-certdump

Static binary similar to the image:

CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o traefik-certdump ./cmd/traefik-certdump

Subcommand for manual probing (same as the image healthcheck):

./traefik-certdump health

Environment variables

Variable Required Default Description
JSON_PATH yes* Absolute path to Traefiks acme.json.
POLL_SECONDS no 3600 Seconds between cycles; must be a positive integer.
ACME_DOMAIN no* With ACME_CERT_OUT and ACME_KEY_OUT, defines one destination; with --config-file, can add another.
ACME_CERT_OUT no* Full path to the PEM chain file.
ACME_KEY_OUT no* Full path to the PEM private key file.
DISABLE_EXPIRY_CHECK no false If true, skips temporal validity checks on the certificate.

* JSON_PATH: required unless json_path is set in the YAML used with --config-file. For destinations: without --config-file, ACME_DOMAIN, ACME_CERT_OUT, and ACME_KEY_OUT are required; with --config-file, you need domains in the file and/or the same env triplet.

License

See the LICENSE file in the repository.