Skip to content

Messages & Calendars Self-Hosting Guide

This guide walks you through deploying Messages (collaborative inbox) and Calendars (calendar app) from La Suite territoriale on a single VPS using Docker Compose and Traefik.

Architecture

                               ┌─────────────┐
                           ───►│  Traefik    │
                          │    │  (HTTPS)    │
                          │    └──────┬──────┘
                          │           │
       ┌──────────────────┼───────────┼──────────────────────────┐
       │                  │           │                          │
       ▼                  ▼           ▼                          ▼
┌──────────────┐  ┌──────────────┐  ┌──────────┐  ┌──────────────────┐
│  Messages    │  │  Calendars   │  │ Keycloak │  │    CalDAV        │
│  Frontend    │  │  Frontend    │  │  (OIDC)  │  │  (SabreDAV/PHP)  │
│  (Caddy)     │  │  (Caddy)     │  │          │  │                  │
└──────┬───────┘  └──────┬───────┘  └────┬─────┘  └────────┬─────────┘
       │                  │               │                 │
       ▼                  ▼               │                 │
┌──────────────┐  ┌──────────────┐        │                 │
│  Backend     │  │  Backend     │        │                 │
│  (Django)    │◄─┤  (Django)    │────────┘                 │
└──────┬───────┘  └──────┬───────┘                          │
       │                  │                                  │
       ▼                  ▼                                  │
┌──────────────┐  ┌──────────────┐                           │
│ PostgreSQL   │  │  Redis       │                           │
│ OpenSearch   │  │  (shared)    │                           │
│ MinIO (S3)   │  └──────────────┘                           │
└──────┬───────┘                                             │
       │                                                     │
       ▼                                                     ▼
┌──────────────┐                                   ┌──────────────────┐
│  MTA-in/out  │                                   │   PostgreSQL     │
│  (Postfix)   │                                   │   (caldav DB)    │
└──────┬───────┘                                   └──────────────────┘
┌──────────────┐
│  Brevo SMTP  │
│  (Relay)     │
└──────────────┘

Services Summary

Service Port/Domain Purpose
Traefik 80, 443 Reverse proxy, TLS termination
Messages Frontend messages.<domain> Next.js app, Caddy reverse proxy
Messages Backend api.messages.<domain> Django REST API
Calendars Frontend calendars.<domain> Next.js app, Caddy reverse proxy
Calendars Backend api.calendars.<domain> Django REST API
Keycloak auth.messages.<domain> OIDC identity provider
CalDAV internal SabreDAV calendar server
MTA-in 25 (SMTP) Inbound email (Postfix)
MTA-out internal Outbound email (Postfix → Brevo)
PostgreSQL internal Shared database
Redis internal Cache & message broker
OpenSearch internal Full-text search (Messages)
MinIO internal S3-compatible object storage

Shared Components

Both applications share:

  • Keycloak — single instance with separate realms (messages, calendars)
  • PostgreSQL — separate databases (messages, calendars, caldav)
  • Redis — on messages_internal Docker network
  • Brevo SMTP — same relay for all outbound email

Key Design Decisions

Static IP Network for Caddy DNS

Caddy's Go DNS resolver caches the backend container's IP. When the backend is recreated, Caddy keeps sending requests to the old IP, causing intermittent 400 errors. Both frontends use a dedicated Docker network with static IPs.

CalDAV Through Django

Calendar operations go through the Django backend proxy (/caldav/*), which adds X-LS-User and API key headers before forwarding to the CalDAV PHP server. Direct CalDAV proxy would return 401 and trigger a frontend re-auth loop.

Messages ↔ Calendars Integration

Calendars communicates with Messages through a server-to-server API channel with specific scopes. Invitations are sent via the Messages API submit endpoint, which routes through Brevo.

Next Steps

Follow the guide sections in order. Each one builds on the previous.