How to Self-Host WildDuck with Docker Compose

What Is WildDuck?

WildDuck is a scalable, open-source IMAP/POP3 mail server that stores all email data in MongoDB instead of the filesystem. This architecture eliminates single points of failure and enables horizontal scaling — add more WildDuck instances behind a TCP load balancer without data migration. It includes a REST API for full server management, built-in 2FA (TOTP and U2F), application-specific passwords, and optional GPG encryption at rest. WildDuck handles mailbox access only — pair it with Haraka for inbound SMTP and ZoneMTA for outbound SMTP. Official site.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 2 GB of free disk space for the application and database
  • 2 GB of RAM minimum (4 GB recommended for production)
  • A domain name with MX records configured
  • Port 25 open for inbound mail (if running the full stack)

Docker Compose Configuration

Create a directory for WildDuck:

mkdir -p ~/wildduck && cd ~/wildduck

Create docker-compose.yml:

services:
  wildduck:
    image: ghcr.io/zone-eu/wildduck:1.46.25
    container_name: wildduck
    ports:
      - "993:993"
      - "143:143"
      - "995:995"
      - "110:110"
      - "8080:8080"
    depends_on:
      mongo:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      - APPCONF_dbs_mongo=mongodb://mongo:27017/wildduck
      - APPCONF_dbs_redis=redis://redis:6379/3
      - APPCONF_api_host=0.0.0.0
      - APPCONF_api_accessToken=change-this-to-a-strong-random-token
    restart: unless-stopped

  mongo:
    image: mongo:7
    container_name: wildduck-mongo
    volumes:
      - mongo_data:/data/db
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: wildduck-redis
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  mongo_data:
  redis_data:

Important: Change APPCONF_api_accessToken to a strong random string (use openssl rand -hex 32). This token authenticates all API requests.

Start the stack:

docker compose up -d

Initial Setup

  1. Verify WildDuck is running: docker compose logs wildduck
  2. Create your first user via the REST API:
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -H "X-Access-Token: your-api-token" \
  -d '{
    "username": "[email protected]",
    "password": "initial-password",
    "name": "Your Name",
    "quota": 1073741824
  }'
  1. Connect your email client (Thunderbird, etc.) using IMAP:
    • Server: your-server-ip
    • IMAP Port: 993 (SSL) or 143 (STARTTLS)
    • POP3 Port: 995 (SSL) or 110 (STARTTLS)
    • Username: [email protected]
    • Password: the password you set

Configuration

Environment Variables

All WildDuck settings can be overridden via APPCONF_* environment variables that map to TOML config paths:

VariableDefaultDescription
APPCONF_dbs_mongomongodb://127.0.0.1:27017/wildduckMongoDB connection string
APPCONF_dbs_redisredis://127.0.0.1:6379/3Redis connection string
APPCONF_api_host127.0.0.1API bind address
APPCONF_api_accessToken(none)API authentication token

REST API

WildDuck’s API provides full server management without editing config files:

EndpointMethodPurpose
/usersPOSTCreate user
/users/{id}GET/PUT/DELETEManage user
/users/{id}/mailboxesGETList mailboxes
/users/{id}/messagesGETList messages
/users/{id}/filtersGET/POSTManage mail filters
/users/{id}/autoreplyPUTSet auto-reply
/users/{id}/aspsPOSTCreate application-specific password
/users/{id}/2fa/totp/setupPOSTEnable TOTP 2FA

Full API documentation: docs.wildduck.email

Ports

PortProtocolPurpose
993IMAPSIMAP over TLS
143IMAPIMAP with STARTTLS
995POP3SPOP3 over TLS
110POP3POP3 with STARTTLS
8080HTTPREST API

Complete Email Stack

WildDuck handles IMAP/POP3 mailbox access only. For a complete email system, add inbound and outbound SMTP:

ComponentServicePurpose
Mailbox accessWildDuckIMAP/POP3 + REST API
Inbound SMTPHaraka + haraka-plugin-wildduckReceive mail on port 25
Outbound SMTPZoneMTA + zonemta-wildduckSend mail on port 587
WebmailRoundcube or SnappymailWeb-based email access

The haraka-plugin-wildduck handles recipient validation, quota enforcement, and message delivery directly to MongoDB — no LMTP or filesystem delivery needed.

Reverse Proxy

The REST API (port 8080) can be placed behind an HTTP reverse proxy. IMAP/POP3 ports should be exposed directly or proxied via HAProxy in TCP mode.

See Reverse Proxy Setup for HTTP proxy configuration.

Backup

Back up these volumes:

  • mongo_datacritical — contains all emails, users, attachments (GridFS), and settings. This is your entire mail store.
  • redis_data — session and cache data. Less critical but speeds up recovery.

Use mongodump for consistent MongoDB backups:

docker compose exec mongo mongodump --out /data/backup
docker cp wildduck-mongo:/data/backup ./mongo-backup-$(date +%Y%m%d)

See Backup Strategy for automated approaches.

Troubleshooting

IMAP Client Cannot Connect

Symptom: Email client shows connection refused or timeout on port 993. Fix: Verify WildDuck is running: docker compose logs wildduck. Check firewall rules for ports 993 and 143. Without TLS certificates configured, use port 143 with STARTTLS or connect without encryption for testing.

API Returns 401 Unauthorized

Symptom: All API requests return 401. Fix: Include the access token in the X-Access-Token header. Verify the token matches APPCONF_api_accessToken in your Docker Compose environment.

MongoDB Connection Errors on Startup

Symptom: WildDuck exits with MongoDB connection errors. Fix: Ensure MongoDB is fully started before WildDuck. The depends_on with service_healthy condition handles this. If using an external MongoDB, verify the connection string and network connectivity.

Emails Not Arriving (When Using Full Stack)

Symptom: Sent emails to your domain don’t appear in mailboxes. Fix: Check Haraka logs for delivery errors. Verify haraka-plugin-wildduck is configured with the correct MongoDB connection string. Confirm the recipient address exists in WildDuck (check via API).

High MongoDB Disk Usage

Symptom: MongoDB data directory grows rapidly. Fix: WildDuck stores attachments in MongoDB via GridFS with deduplication. Large mailboxes with many unique attachments will consume significant disk space. Monitor with db.stats() in mongosh. Set user quotas via the API to limit storage per user.

Resource Requirements

  • RAM: ~300 MB idle (WildDuck + MongoDB + Redis), 1-2 GB under load
  • CPU: Low-Medium — MongoDB handles most of the heavy lifting
  • Disk: 500 MB for application, plus email storage in MongoDB (plan for 1-10 GB per active user)

Verdict

WildDuck takes a fundamentally different approach to email storage by putting everything in MongoDB. This makes horizontal scaling straightforward — something that’s nearly impossible with traditional filesystem-based mail servers like Mailcow or Mailu. The trade-off is complexity: you need separate components for SMTP (Haraka + ZoneMTA), and MongoDB administration adds operational overhead. Choose WildDuck if you’re building email infrastructure for hundreds or thousands of users and need horizontal scalability. For a personal or small-team email server, Mailcow or Stalwart are simpler choices that bundle everything together.

Comments