Sign inSign up

secbyd/proton-carddav-sync

By secbyd

•Updated 4 days ago

Sync Proton Mail contacts with any CardDAV server (Nextcloud, Radicale, Baikal).

Image
Integration & delivery
0

1.4K

secbyd/proton-carddav-sync repository overview

⁠proton-carddav-sync

Sync your Proton Mail contacts with any CardDAV server — Nextcloud, Radicale, Baïkal, iCloud, and others. A small, self-hosted Go daemon.

📦 Source & full docs: https://github.com/secbyd/proton-carddav-sync⁠

⁠What it does

  • Bidirectional sync (Proton ↔ CardDAV) with a per-property three-way merge, so changing one field on one side never wipes fields the other side has. One-way modes are available too.
  • Password-free, long-lasting Proton session (à la hydroxide/ferroxide): you log in once; the daemon resumes via a refresh token and never stores your account password.
  • Encrypted at rest — the Proton session and CardDAV password are AES-256-GCM encrypted in a local SQLite database with a key derived from PCS_ENCRYPTION_KEY. Nothing secret is written to the config file.
  • Gentle on Proton — all Proton API calls are rate-limited (default ~1/sec) to stay well under Proton's anti-abuse limits, with an optional per-run cap on new contacts for large first imports.
  • TOTP/2FA supported during setup.

⁠Tags & architectures

  • latest — the current main build
  • vX.Y.Z — tagged releases
  • sha-<short> — a specific commit

Images are multi-arch: linux/amd64 and linux/arm64.

⁠Volumes & configuration

MountPurpose
/configholds config.yaml (set database.path: /data/sync.db)
/dataholds the SQLite database (credentials + sync state)

PCS_ENCRYPTION_KEY (a long random passphrase) must be passed at runtime — it is never baked into the image, and the same value is required by init and the daemon.

The container runs as a non-root user (uid 10001). Named volumes (as below) work out of the box. If you bind-mount host directories owned by another user, run the container as that user — --user $(id -u):$(id -g) (or user: "1000:1000" in compose), applied to the init run too — or chown -R 10001:10001 the host directories.

⁠Usage

⁠1. Initialise (interactive — prompts for passwords + TOTP)
docker run --rm -it \
  -e PCS_ENCRYPTION_KEY="a-long-random-passphrase" \
  -v pcs-config:/config -v pcs-data:/data \
  secbyd/proton-carddav-sync \
  init --config /config/config.yaml

With no config present, init asks for every setting (set database.path to /data/sync.db), logs in to Proton (asking for a TOTP code if 2FA is enabled), and stores the encrypted session.

If your platform can't run an interactive one-off container (e.g. Portainer or Unraid), start the container with the idle command instead, then:

docker exec -it <container> proton-carddav-sync init --config /config/config.yaml

…and restart it with the default command afterwards.

⁠2. Run the daemon
docker run -d --name proton-carddav-sync --restart unless-stopped \
  -e PCS_ENCRYPTION_KEY="a-long-random-passphrase" \
  -v pcs-config:/config -v pcs-data:/data \
  secbyd/proton-carddav-sync
⁠docker compose
services:
  proton-carddav-sync:
    image: secbyd/proton-carddav-sync:latest
    restart: unless-stopped
    environment:
      PCS_ENCRYPTION_KEY: "a-long-random-passphrase"
    volumes:
      - pcs-config:/config
      - pcs-data:/data
volumes:
  pcs-config:
  pcs-data:

Run init once before up -d: docker compose run --rm -it proton-carddav-sync init --config /config/config.yaml

⁠Environment variables

VariableRequiredDescription
PCS_ENCRYPTION_KEYyesMaster key encrypting the stored Proton session + CardDAV password
PCS_PROTON_APP_VERSIONnoOverride the Proton x-pm-appversion header
PCS_PROTON_USER_AGENTnoOverride the browser User-Agent sent to Proton

⁠Notes

  • Proton enforces a strict Acceptable Use Policy. A first sync of a large address book is paced automatically; for hundreds of contacts you can also cap new creates per run. See the GitHub README for details.
  • Built on a glibc debian:bookworm-slim base so the CGO SQLite driver works without musl/static-link complications.

⁠License

See the repository⁠ for license and full documentation.

Tag summary

Content type

Image

Digest

sha256:120c18de3…

Size

37.1 MB

Last updated

4 days ago

docker pull secbyd/proton-carddav-sync