No description
Find a file
paspo f9038878c0
All checks were successful
Container Publish / on-success-skip (push) Has been skipped
Container Publish / build-image (amd64) (push) Successful in 52s
Container Publish / build-image (arm64) (push) Successful in 1m8s
Container Publish / update docker manifest (push) Successful in 9s
translated readme
2026-03-30 17:57:30 +02:00
.forgejo/workflows fix build pipeline 2026-03-21 13:47:25 +01:00
src go rewrite 2026-03-30 17:56:09 +02:00
Dockerfile go rewrite 2026-03-30 17:56:09 +02:00
LICENSE license 2026-03-21 13:27:17 +01:00
README.md translated readme 2026-03-30 17:57:30 +02:00

docker-traefik-vault-sync

A container that reads TLS certificates from HashiCorp Vault (KV v2) and periodically updates the files used by Traefik through the file provider (dynamic tls configuration).

Expected Vault structure (configurable prefix, default secret/acme):

  • kv list on the base path returns domain names (folders).
  • For each domain example.com:
    • ${VAULT_ACME_PREFIX}/example.com/fullchain.pem -> certificate value (value field in .data.data)
    • ${VAULT_ACME_PREFIX}/example.com/cert.key -> private key value

Script-specific variables:

Variable Default Description
SLEEP_SECONDS 300 Interval in seconds between sync cycles.
VAULT_ACME_PREFIX secret/acme KV v2 path (without secret/data/...) where certificate secrets live.

Vault CLI variables (hashicorp/vault image), typically required:

Variable Description
VAULT_ADDR Vault server URL (for example, https://vault:8200).
VAULT_TOKEN Token with read permissions on the target KV paths.

Common optional variables: VAULT_NAMESPACE, VAULT_SKIP_VERIFY (test environments only), VAULT_CACERT, etc. (Vault documentation)

Docker Compose (Traefik v3)

Minimal example: Traefik and the sync container share certs and dynamic volumes; the sync writes certificates and tls.yml, Traefik reads them.

services:
  traefik:
    image: traefik:v3
    user: "10001:10001"
    cap_add:
      - NET_BIND_SERVICE
    command:
      - --api.dashboard=true
      - --providers.file.directory=/dynamic
      - --providers.file.watch=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - certs:/certs
      - dynamic:/dynamic:ro

  vault-sync:
    image: docker.asperti.com/paspo/traefik-vault-sync:latest
    user: "10001:10001"
    environment:
      VAULT_ADDR: https://vault.example.com:8200
      VAULT_TOKEN: ${VAULT_TOKEN}
      VAULT_ACME_PREFIX: secret/acme
      SLEEP_SECONDS: 300
    volumes:
      - certs:/certs
      - dynamic:/dynamic
    restart: unless-stopped

volumes:
  certs:
  dynamic:

Set VAULT_TOKEN in a local .env file (do not commit it) or via your orchestrator secret mechanism.

The generated /dynamic/tls.yml file uses tls.certificates with certFile / keyFile under /certs/..., matching the mounts above.

NET_BIND_SERVICE allows Traefik to bind to :80 and :443 even when running as non-root.

Align vault-sync user: with Traefik (UID:GID must match).

For each domain, the script compares the Vault timestamp (.data.metadata.created_time) with local mtime: it updates /certs/<domain>.crt and /certs/<domain>.key only if the file does not exist or the timestamp changed, and logs updates to stdout.

User and Permissions (runtime)

The image uses a non-root user by default (1001:1001) and does not run runtime chown. If needed, override UID:GID in docker-compose via user:.

Local Build

docker build -t docker-traefik-vault-sync:local .

Healthcheck

The container uses HEALTHCHECK and directly runs the binary with the health subcommand. No health file is written.