Skip to content

Calendars: CalDAV Server

CalDAV stores calendar data in PostgreSQL with SabreDAV (PHP). It must be built from source since there's no pre-built image.

Building the Image

git clone --depth 1 https://github.com/suitenumerique/calendars.git /tmp/calendars
docker build -t calendars-caldav /tmp/calendars/src/caldav/

Configuration

The CalDAV container needs:

  1. PostgreSQL connection to the caldav database (created on the shared Messages PostgreSQL)
  2. API keys matching the Calendars backend
  3. Callback URL pointing to the Calendars backend for invitation scheduling
  4. Database initialization on first boot
caldav:
  image: calendars-caldav:latest
  command: >
    sh -c "
      /usr/local/bin/init-database.sh || true &&
      apache2-foreground
    "
  environment:
    PGHOST: postgresql
    PGPORT: 5432
    PGDATABASE: caldav
    PGUSER: messages
    PGPASSWORD: <db-password>
    CALDAV_DB_SCHEMA: sabre
    CALDAV_BASE_URI: /caldav/
    CALDAV_INBOUND_API_KEY: <api-key>
    CALDAV_OUTBOUND_API_KEY: <api-key>
    CALDAV_INTERNAL_API_KEY: <api-key>
    CALDAV_CALLBACK_BASE_URL: http://172.29.0.30:8000

Database Setup

Before starting CalDAV, create the database on the Messages PostgreSQL:

docker compose exec -T postgresql psql -U messages -c "CREATE DATABASE caldav;"

The init-database.sh script creates the SabreDAV schema tables on first boot.

API Keys

Three API keys must match between the CalDAV server and the Calendars backend:

Key Purpose
CALDAV_INBOUND_API_KEY CalDAV → Django callbacks (scheduling)
CALDAV_OUTBOUND_API_KEY Client → CalDAV auth (ApiKeyAuthBackend)
CALDAV_INTERNAL_API_KEY Internal CalDAV plugins

Generate a single key and use it for all three.

Callback URL

The CALDAV_CALLBACK_BASE_URL must point to the Calendars backend using the static IP, not backend hostname:

http://172.29.0.30:8000   # Correct
http://backend:8000        # Wrong — DNS conflict with Messages backend

Both compose files have a service named backend. On the shared messages_internal network, Docker DNS resolves backend to both containers, causing random routing failures.

Callback Endpoint

The CalDAV server constructs the full callback URL as:

{CALDAV_CALLBACK_BASE_URL}/api/v1.0/caldav-scheduling-callback/

This Django endpoint processes CalDAV scheduling messages (iMIP) and sends invitation emails. It requires:

  1. X-LS-Api-Key header (matching CALDAV_INBOUND_API_KEY)
  2. 172.29.0.30 in DJANGO_ALLOWED_HOSTS
  3. messages:send scope on the Messages API channel