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
- Verify WildDuck is running:
docker compose logs wildduck - 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
}'
- 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:
| Variable | Default | Description |
|---|---|---|
APPCONF_dbs_mongo | mongodb://127.0.0.1:27017/wildduck | MongoDB connection string |
APPCONF_dbs_redis | redis://127.0.0.1:6379/3 | Redis connection string |
APPCONF_api_host | 127.0.0.1 | API bind address |
APPCONF_api_accessToken | (none) | API authentication token |
REST API
WildDuck’s API provides full server management without editing config files:
| Endpoint | Method | Purpose |
|---|---|---|
/users | POST | Create user |
/users/{id} | GET/PUT/DELETE | Manage user |
/users/{id}/mailboxes | GET | List mailboxes |
/users/{id}/messages | GET | List messages |
/users/{id}/filters | GET/POST | Manage mail filters |
/users/{id}/autoreply | PUT | Set auto-reply |
/users/{id}/asps | POST | Create application-specific password |
/users/{id}/2fa/totp/setup | POST | Enable TOTP 2FA |
Full API documentation: docs.wildduck.email
Ports
| Port | Protocol | Purpose |
|---|---|---|
| 993 | IMAPS | IMAP over TLS |
| 143 | IMAP | IMAP with STARTTLS |
| 995 | POP3S | POP3 over TLS |
| 110 | POP3 | POP3 with STARTTLS |
| 8080 | HTTP | REST API |
Complete Email Stack
WildDuck handles IMAP/POP3 mailbox access only. For a complete email system, add inbound and outbound SMTP:
| Component | Service | Purpose |
|---|---|---|
| Mailbox access | WildDuck | IMAP/POP3 + REST API |
| Inbound SMTP | Haraka + haraka-plugin-wildduck | Receive mail on port 25 |
| Outbound SMTP | ZoneMTA + zonemta-wildduck | Send mail on port 587 |
| Webmail | Roundcube or Snappymail | Web-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_data— critical — 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.
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments