How to Self-Host Davis with Docker Compose
What Is Davis?
Davis is a self-hosted CalDAV and CardDAV server built on the sabre/dav library with a Symfony backend. It provides calendar and contact synchronization across all your devices — phones, tablets, desktops — without relying on Google, Apple, or Microsoft. Davis includes a clean web dashboard for managing users, calendars, and address books, plus support for LDAP and IMAP authentication. It replaces Google Calendar, Google Contacts, and iCloud calendar/contacts sync.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 256 MB of free RAM
- 500 MB of free disk space
- A domain name (recommended for CalDAV/CardDAV client configuration)
Docker Compose Configuration
Davis offers two Docker images: davis (requires an external web server like nginx) and davis-standalone (includes nginx — easier to set up). This guide uses the standalone image.
Create a docker-compose.yml file:
services:
davis:
image: ghcr.io/tchapi/davis-standalone:5.3.0
container_name: davis
restart: unless-stopped
ports:
- "9000:8080"
environment:
APP_ENV: prod
APP_SECRET: ${APP_SECRET}
APP_TIMEZONE: ${APP_TIMEZONE:-America/New_York}
DATABASE_DRIVER: mysql
DATABASE_URL: mysql://davis:${DB_PASSWORD}@davis-db:3306/davis?serverVersion=10.11.6-MariaDB&charset=utf8mb4
ADMIN_LOGIN: ${ADMIN_LOGIN:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
CALDAV_ENABLED: "true"
CARDDAV_ENABLED: "true"
WEBDAV_ENABLED: "false"
AUTH_REALM: SabreDAV
AUTH_METHOD: Basic
INVITE_FROM_ADDRESS: [email protected]
depends_on:
davis-db:
condition: service_healthy
davis-db:
image: mariadb:10.11
container_name: davis-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: davis
MYSQL_USER: davis
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- davis_db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
volumes:
davis_db:
Create a .env file:
# Generate with: openssl rand -hex 32
APP_SECRET=CHANGE_ME_TO_A_RANDOM_HEX_STRING
# Timezone — see https://www.php.net/manual/en/timezones.php
APP_TIMEZONE=America/New_York
# Admin dashboard credentials — CHANGE THESE
ADMIN_LOGIN=admin
ADMIN_PASSWORD=CHANGE_ME_TO_A_STRONG_PASSWORD
# Database passwords — CHANGE THESE
DB_PASSWORD=CHANGE_ME_DAVIS_DB_PASSWORD
DB_ROOT_PASSWORD=CHANGE_ME_ROOT_DB_PASSWORD
Start the stack:
docker compose up -d
After first start, run the database migrations:
docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"
Initial Setup
- Open
http://your-server-ip:9000in your browser - Log in with the admin credentials from your
.envfile - The dashboard shows the server status — CalDAV and CardDAV should both show as enabled
- Create user accounts via the dashboard — each user gets their own calendars and address books
Connecting Clients
Davis uses standard CalDAV/CardDAV protocols. Connection URLs:
| Protocol | URL |
|---|---|
| CalDAV | https://your-domain.com/dav/calendars/USERNAME/ |
| CardDAV | https://your-domain.com/dav/addressbooks/USERNAME/ |
| Auto-discovery | https://your-domain.com/.well-known/caldav |
iOS/macOS: Settings → Calendar → Accounts → Add Account → Other → CalDAV. Enter your server URL, username, and password. iOS auto-discovers both calendars and contacts from the CalDAV URL.
Android: Use DAVx⁵ (F-Droid or Play Store). Add a new account with your server URL. DAVx⁵ handles both CalDAV and CardDAV.
Thunderbird: Calendar → New Calendar → Network → CalDAV. Contacts → New Address Book → CardDAV.
Configuration
Key Environment Variables
| Variable | Default | Description |
|---|---|---|
APP_SECRET | — | Required. Symfony secret for session encryption |
APP_TIMEZONE | — | Server timezone for calendar display |
DATABASE_DRIVER | mysql | Database backend: mysql, postgresql, or sqlite |
DATABASE_URL | — | Required. Database connection string |
ADMIN_LOGIN | admin | Dashboard admin username |
ADMIN_PASSWORD | test | Dashboard admin password — change this |
CALDAV_ENABLED | true | Enable calendar support |
CARDDAV_ENABLED | true | Enable contacts support |
WEBDAV_ENABLED | false | Enable file storage via WebDAV |
AUTH_METHOD | Basic | Authentication method: Basic, IMAP, or LDAP |
PUBLIC_CALENDARS_ENABLED | true | Allow calendars to be shared publicly |
INVITE_FROM_ADDRESS | — | Sender address for calendar invitations |
Using SQLite (Simpler Setup)
For small deployments (1-5 users), SQLite eliminates the need for a separate database container:
services:
davis:
image: ghcr.io/tchapi/davis-standalone:5.3.0
container_name: davis
restart: unless-stopped
ports:
- "9000:8080"
environment:
APP_ENV: prod
APP_SECRET: ${APP_SECRET}
DATABASE_DRIVER: sqlite
DATABASE_URL: "sqlite:///%kernel.project_dir%/var/davis.db"
ADMIN_LOGIN: admin
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
CALDAV_ENABLED: "true"
CARDDAV_ENABLED: "true"
volumes:
- davis_data:/var/www/davis/var
volumes:
davis_data:
LDAP Authentication
Connect Davis to an LDAP server (like LLDAP or OpenLDAP) for centralized user management:
AUTH_METHOD=LDAP
LDAP_AUTH_URL=ldap://your-ldap-server:3890
LDAP_DN_PATTERN=uid=%u,ou=people,dc=yourdomain,dc=com
LDAP_MAIL_ATTRIBUTE=mail
LDAP_AUTH_USER_AUTOCREATE=true
Reverse Proxy
CalDAV and CardDAV clients rely on .well-known auto-discovery URLs. Your reverse proxy must forward these paths:
/.well-known/caldav → http://davis:8080
/.well-known/carddav → http://davis:8080
In Nginx Proxy Manager, set the scheme to http, hostname to davis, and port to 8080. Enable SSL with Let’s Encrypt.
See Reverse Proxy Setup for full configuration.
Backup
Back up the database volume:
- MariaDB:
docker exec davis-db mysqldump -u davis -p davis > davis_backup.sql - SQLite: Back up the
davis_datavolume directly
Calendar and contact data lives entirely in the database — there are no separate data files to back up.
See Backup Strategy for automated backup workflows.
Troubleshooting
500 error on first access
Symptom: Browser shows a 500 Internal Server Error after starting the containers. Fix: You likely forgot to run the database migrations. Execute:
docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"
“Bad timezone configuration env var” on the dashboard
Symptom: Dashboard shows a timezone warning.
Fix: The APP_TIMEZONE value is not a valid PHP timezone. Check the official list and update your .env file.
Clients cannot discover calendars
Symptom: iOS or DAVx⁵ says “no calendars found.”
Fix: Ensure your reverse proxy forwards /.well-known/caldav and /.well-known/carddav to the Davis container. Also verify the user has at least one calendar created via the admin dashboard.
LDAP connection failures
Symptom: Users cannot log in with LDAP credentials. Fix: Ensure Davis and the LDAP server share a Docker network. Test connectivity from inside the container:
docker exec -it davis sh -c "apk add openldap-clients && ldapsearch -H ldap://your-ldap:3890 -D 'uid=testuser,ou=people,dc=domain,dc=com' -W"
Check that LDAP_DN_PATTERN matches your LDAP directory structure. LLDAP uses ou=people, while OpenLDAP often uses ou=users.
Resource Requirements
- RAM: ~80 MB idle, ~150 MB under load
- CPU: Very low — CalDAV/CardDAV is lightweight
- Disk: ~200 MB for the application, database grows slowly (calendars and contacts are small)
Verdict
Davis is the most polished CalDAV/CardDAV server for self-hosters who want a web dashboard and modern authentication options. The standalone Docker image makes deployment straightforward, and LDAP support integrates cleanly with existing identity infrastructure. For users who only need basic calendar/contact sync without a web UI, Radicale is lighter — it runs in a single Python process with file-based storage. Baikal sits between the two, offering a simpler admin interface but less flexibility on authentication. Davis is the right choice when you need LDAP/IMAP auth, multiple users, or public calendar sharing.
Related
- How to Self-Host Radicale — lightweight file-based CalDAV/CardDAV
- How to Self-Host Baikal — simple CalDAV/CardDAV with PHP
- Radicale vs Baikal — comparison of simpler alternatives
- Replace Google Calendar — migration guide
- Replace Google Contacts — migration guide
- Best Self-Hosted Calendar & Contacts — full category roundup
- Docker Compose Basics — prerequisite guide
- Reverse Proxy Setup — remote access configuration
Get self-hosting tips in your inbox
New guides, comparisons, and setup tutorials — delivered weekly. No spam.