How to Self-Host EteSync with Docker Compose

EteSync started as a privacy-focused alternative to Google Calendar and Contacts, adding end-to-end encryption that no other CalDAV server offers. The current version — Etebase (EteSync 2.0) — encrypts all data client-side before it reaches the server, so even a compromised server reveals nothing. If zero-knowledge sync is your requirement, this is the only self-hosted option that delivers it.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 512 MB of free RAM
  • 500 MB of free disk space
  • A domain name with HTTPS (required — CalDAV clients reject plaintext connections)

Docker Compose Configuration

Etebase uses an INI configuration file rather than environment variables. You’ll need to create the config file before starting the container.

First, create the configuration file etebase-server.ini:

[global]
secret_file = /data/secret.txt
debug = false
static_root = /srv/etebase/static
media_root = /srv/etebase/media

[allowed_hosts]
; CHANGE THIS — your actual domain name
allowed_host1 = etebase.example.com

[database]
engine = django.db.backends.sqlite3
name = /data/db.sqlite3

For production with PostgreSQL, replace the [database] section:

[database]
engine = django.db.backends.postgresql
name = etebase

[database-options]
host = etebase-db
port = 5432
user = etebase
password = change-this-db-password

Create the docker-compose.yml:

services:
  etebase:
    image: victorrds/etebase:0.14.2
    container_name: etebase
    restart: unless-stopped
    ports:
      - "3735:3735"
    environment:
      SERVER: "asgi"
      # Auto-generate admin account on first run
      AUTO_SIGNUP: "false"
      SUPER_USER: "admin"
      SUPER_PASS: "change-this-admin-password"
      SUPER_EMAIL: "[email protected]"
    volumes:
      - etebase_data:/data
      - ./etebase-server.ini:/etc/etebase-server/etebase-server.ini:ro
    depends_on:
      etebase-db:
        condition: service_healthy

  etebase-db:
    image: postgres:16-alpine
    container_name: etebase-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: "etebase"
      POSTGRES_PASSWORD: "change-this-db-password"
      POSTGRES_DB: "etebase"
    volumes:
      - etebase_db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U etebase"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  etebase_data:
  etebase_db:

Start the stack:

docker compose up -d

Initial Setup

  1. Open https://etebase.example.com in a browser (HTTPS required)
  2. The admin account was created automatically via the SUPER_USER/SUPER_PASS environment variables on first run
  3. Log into the Django admin at https://etebase.example.com/admin/ to manage users
  4. Disable AUTO_SIGNUP in the environment if you don’t want open registration

Connecting Client Apps

EteSync uses its own protocol — not standard CalDAV directly. You have two options:

Option 1: EteSync native apps (recommended)

Option 2: CalDAV/CardDAV bridge (for standard clients)

  • Install DAVx⁵ on Android with the EteSync plugin
  • Use the etesync-dav bridge for desktop CalDAV clients

Enter your server URL (https://etebase.example.com), username, and password in the client app. The app handles encryption key generation transparently.

Configuration

Security Settings

SettingValuePurpose
AUTO_SIGNUPfalsePrevent open registration on public servers
debugfalseNever enable in production — exposes internal state
allowed_host1Your domainDjango rejects requests from unlisted hosts

Secret Key

Etebase auto-generates a secret.txt file in /data/ on first run. This file is critical — it signs authentication tokens and encrypts session data. Back this file up. Losing it makes the database inaccessible.

SQLite vs PostgreSQL

SQLite works for personal use (1-3 users, under 10,000 entries). Switch to PostgreSQL for:

  • Multiple concurrent users
  • Large calendar/contact collections
  • Better backup reliability (pg_dump vs file copy)

Reverse Proxy

The Etebase container serves on port 3735. Put it behind a reverse proxy with HTTPS — CalDAV clients require TLS.

For Caddy:

etebase.example.com {
    reverse_proxy localhost:3735
}

For Nginx Proxy Manager, forward port 3735 with SSL enabled and WebSocket support (not strictly required but doesn’t hurt). Set proxy_set_header Host $host; — Etebase validates the Host header against allowed_hosts.

See the Reverse Proxy Setup guide for detailed instructions.

Backup

DataLocationPriority
Secret key/data/secret.txtCritical — losing this makes database unrecoverable
DatabasePostgreSQL volume or /data/db.sqlite3Critical — all user data and encrypted blobs
Media files/srv/etebase/media (inside container)Critical — uploaded attachments
# PostgreSQL backup
docker exec etebase-db pg_dump -U etebase etebase > etebase_backup.sql

# SQLite backup (if using SQLite)
docker cp etebase:/data/db.sqlite3 ./etebase-db-backup.sqlite3

# Secret key backup
docker cp etebase:/data/secret.txt ./etebase-secret.txt.backup

See the Backup Strategy guide for automated workflows.

Troubleshooting

”Bad Request (400)” on Every Request

Symptom: Browser shows 400 error, logs show DisallowedHost. Fix: The allowed_hosts setting in etebase-server.ini must include your exact domain. If accessing via IP, add the IP as another allowed_host2 = 192.168.1.x. Restart the container after changes.

CalDAV Clients Can’t Connect

Symptom: Thunderbird or iOS Calendar refuses to connect. Fix: Standard CalDAV clients don’t support the Etebase protocol directly. You need either the EteSync native app or the etesync-dav bridge to translate between protocols. DAVx⁵ on Android supports EteSync natively with the plugin.

Admin Account Not Created

Symptom: Can’t log into /admin/ after first run. Fix: The SUPER_USER and SUPER_PASS environment variables only take effect on the very first container start. If the database already exists, create an admin manually:

docker exec -it etebase python manage.py createsuperuser

Data Not Syncing Between Devices

Symptom: Changes on one device don’t appear on another. Fix: Verify both devices point to the same server URL. EteSync uses conflict-free sync — changes may take a few minutes to propagate. Check the sync log in the client app. If using the DAV bridge, ensure the bridge is running and connected to the Etebase server.

Resource Requirements

ResourceMinimumRecommended
RAM256 MB512 MB
CPU1 core2 cores
Disk500 MB (app) + data1 GB + data

Etebase is lightweight for a Django application. The encryption operations are handled client-side, so the server’s CPU load is minimal. With PostgreSQL, add another 256 MB for the database.

Verdict

EteSync (Etebase) is the only self-hosted calendar/contacts server that provides true end-to-end encryption. If you need zero-knowledge guarantees — where even the server admin can’t read your calendar events — this is it. The encryption is properly implemented and the protocol is well-documented.

The trade-off is real though: limited CalDAV client compatibility (you need native apps or bridges), a smaller community than Radicale or Baikal, and slower development pace. For most self-hosters who already control their server and trust their disk encryption, Radicale offers a simpler path. But for privacy-first users, EteSync is worth the extra setup.

FAQ

What’s the difference between EteSync and Etebase?

Etebase is the rewritten protocol and server (EteSync 2.0). The old EteSync 1.x protocol is deprecated. New deployments should always use Etebase. The two protocols are not compatible — you can’t upgrade an EteSync 1.x server to Etebase without a fresh install.

Does EteSync work with Apple Calendar on iPhone?

Not directly. iOS Calendar only supports standard CalDAV, not the Etebase protocol. Use the official EteSync iOS app for native encrypted sync, or set up the etesync-dav bridge on your server to expose a CalDAV interface.

Can I use EteSync for notes and tasks too?

Yes. Etebase supports encrypted calendar events, contacts, tasks (VTODO), and notes. The EteSync Notes app provides encrypted note sync across devices.

How is the encryption different from just using HTTPS?

HTTPS encrypts data in transit. EteSync encrypts data at rest on the server too — using keys that only exist on your devices. Even with full server access and database dumps, your calendar events are encrypted blobs without the device key.

Comments