Radicale¶
Radicale is a small but powerful CalDAV (calendar) and CardDAV (contact) server. It is written in Python and is designed to be lightweight, standards-compliant, and easy to set up.
What it is¶
Radicale is an open-source CalDAV and CardDAV server that allows you to host your own calendars and contacts. In early January 2027, the stable version is v3.8.x, which continues to focus on a simple, file-based storage format (iCalendar and vCard), making backups and data ownership straightforward. It is a cornerstone of privacy-first personal information management.
What problem it solves¶
It provides a private, self-hosted alternative to cloud-based synchronization services (like Google Calendar or iCloud). By using standard protocols, it allows for seamless syncing across a wide variety of devices and applications while keeping the user in full control of their scheduling and contact data, eliminating third-party tracking.
Where it fits in the stack¶
Radicale serves as the Intake & Storage layer for personal information management (PIM) within a home-office or homelab ecosystem. It is often integrated with Claude 5.1, Claude 5.6, GPT-5.5, GPT-5.6, Gemini 4.0 Pro/Ultra, DeepSeek-V4, Llama 4, or Gemma 3 via FastMCP 3.1 Task Protocol to allow AI agents to manage appointments and contacts using natural language and standardized execution patterns.
Typical use cases¶
- Syncing personal and family calendars across desktops (Thunderbird) and mobile devices (Android/iOS via DAVx⁵).
- Hosting a private address book that is accessible from multiple devices.
- Serving as a backend for task management tools that support the CalDAV protocol.
- Providing an automated audit trail for scheduling changes via Git-based versioning.
- Exposing scheduling data to agentic workflows for automated meeting preparation.
Strengths¶
- Lightweight: Minimal resource footprint, suitable for Raspberry Pi or low-power containers.
- Simple Storage: Uses standard
.icsand.vcffiles on disk, ensuring no vendor lock-in. - Extensible: Supports multiple authentication backends (htpasswd, LDAP, remote user).
- Standards-Compliant: High compatibility with various CalDAV/CardDAV clients.
- Git Integration: Native support for versioning collections via git hooks.
Limitations¶
- No Built-in Web Client: Lacks a full-featured web interface for managing events (primarily an admin UI).
- Single-Server Focus: Not intended for large-scale enterprise deployments with thousands of users.
- Manual Hardening: Requires a reverse proxy (like Nginx or Caddy) for proper SSL/TLS and advanced security.
When to use it¶
- When you want a simple, privacy-focused solution for syncing calendars and contacts.
- If you value owning your data in a transparent, file-based format.
- For small teams or families needing a shared scheduling backend.
- When integrating calendar data into local AI agent memory via MCP.
When not to use it¶
- If you require integrated email, document collaboration, or a native web calendar (consider Nextcloud).
- In environments requiring complex resource booking or advanced delegation features.
- If you prefer a database-backed storage for high-concurrency write operations.
Getting started¶
Installation¶
Install Radicale using pip:
python3 -m pip install --upgrade radicale
Docker Compose¶
For containerized deployments (standard for TrueNAS SCALE or Docker-based homelabs):
services:
radicale:
image: tomsquest/docker-radicale:latest
container_name: radicale
ports:
- "5232:5232"
volumes:
- ./data:/data
- ./config:/config:ro
restart: unless-stopped
user: "1000:1000"
Git-based Versioning¶
Radicale can automatically version your collections using Git. Initialize a git repo in your collections directory and add the following to your config.ini:
[hook]
after_save = git add . && git commit -m "Radicale change"
Hello World¶
- Access the admin UI at
http://localhost:5232. - Create a new collection and select Calendar.
- Name it "Work" and click Create.
- Use the provided URL in your calendar client (e.g., Thunderbird) with your configured credentials.
CLI examples¶
The radicale module provides utilities for maintenance and integrity checks:
# Verify the integrity of the local collections storage
python3 -m radicale --verify-storage
# Check the installed version and active configuration paths
python3 -m radicale --version --debug
# Export a specific collection to a single .ics file for backup
python3 -m radicale --export /path/to/collection > personal_backup.ics
API examples¶
Radicale follows the standard CalDAV/CardDAV (HTTP-based) protocol. Below is a Python API validator utilizing Pydantic v2 to structure calendars parsed from Radicale.
import requests
import xml.etree.ElementTree as ET
from pydantic import BaseModel, Field
from typing import List, Optional
# Define Radicale parsed collection schema in Pydantic v2
class CalendarCollection(BaseModel):
displayname: str = Field(..., description="The user-friendly name of the Radicale collection")
href: str = Field(..., description="The relative URI path of the collection")
owner: str = Field(..., description="The owner/user associated with the calendar path")
class CalendarListResponse(BaseModel):
collections: List[CalendarCollection]
def list_radicale_calendars(username: str, password: str, base_url: str = "http://localhost:5232") -> CalendarListResponse:
url = f"{base_url}/{username}/"
headers = {"Depth": "1"}
# PROPFIND is the standard WebDAV/CalDAV method used to list resources
response = requests.request(
"PROPFIND",
url,
auth=(username, password),
headers=headers
)
response.raise_for_status()
# Parse CalDAV XML namespace response
root = ET.fromstring(response.content)
namespaces = {
'D': 'DAV:',
'C': 'urn:ietf:params:xml:ns:caldav'
}
collections = []
# Find all D:response elements representing collections
for resp in root.findall('D:response', namespaces):
href = resp.find('D:href', namespaces)
href_text = href.text if href is not None else ""
# Skip the parent collection root path
if href_text == f"/{username}/" or not href_text:
continue
propstat = resp.find('.//D:prop', namespaces)
displayname = "Unnamed Collection"
if propstat is not None:
disp_el = propstat.find('D:displayname', namespaces)
if disp_el is not None and disp_el.text:
displayname = disp_el.text
try:
col = CalendarCollection(
displayname=displayname,
href=href_text,
owner=username
)
collections.append(col)
except Exception as e:
print(f"Skipping invalid calendar path: {e}")
return CalendarListResponse(collections=collections)
# Example usage
if __name__ == "__main__":
calendars = list_radicale_calendars("admin", "your_password")
for cal in calendars.collections:
print(f"Validated Calendar: {cal.displayname} -> Path: {cal.href}")
Related tools / concepts¶
- Vikunja — Task management that can sync with Radicale.
- Authentik — For unified SSO and OIDC authentication.
- Tailscale — Secure remote access to your Radicale instance.
- Home Assistant — For integrating calendars into home automation.
- n8n — For automating scheduling workflows (e.g., meeting reminders).
- Chronos MCP — To expose CalDAV data to AI agents.
- Local LLMs Guide — Reference for Gemma 3 and other models.
Sources / References¶
- Official Radicale Project Website
- Radicale Source Code Repository
- Radicale Documentation v3
- Model Context Protocol Specification
Contribution Metadata¶
- Last reviewed: 2027-01-07
- Confidence: high