Kubernetes setup



There’s chosen to have a double NGINX stack for Mailu, this way the main ingress can still be used to access other websites/domains on your cluster. This is the current structure:

  • NGINX Ingress controller: Listens to the nodes ports 80 & 443. We have chosen to have a double NGINX stack for Mailu.
  • Cert manager: Creates automatic Lets Encrypt certificates based on an Ingress-objects domain name.
  • Mailu NGINX Front daemonset: This daemonset runs in parallel with the Nginx Ingress Controller and only listens on all E-mail specific ports (25, 110, 143, 587,…)
  • Mailu components: All Mailu components (imap, smtp, security, webmail,…) are split into separate files to make them more handy to use, you can find the YAML files in this directory

What you need

  • A working Kubernetes cluster (tested with 1.10.5)
  • A working cert-manager installation
  • A working nginx-ingress controller needed for the lets-encrypt certificates. You can find those files in the nginx subfolder

Cert manager

The Cert-manager is quite easy to deploy using Helm when reading the docs. After booting the Cert-manager you’ll need a ClusterIssuer which takes care of all required certificates through Ingress items. We chose to provide a clusterIssuer so you can provide SSL certificates for other namespaces (different websites/services), if you don’t need this option, you can easily change this by changing clusterIssuer to Issuer and adding the namespace: mailu-mailserver to the metadata. An example of a production and a staging clusterIssuer:

# This clusterIssuer example uses the staging environment for testing first
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
  name: letsencrypt-stage
    email: something@example.com
    http01: {}
      name: letsencrypt-stage
    server: https://acme-staging-v02.api.letsencrypt.org/directory
# This clusterIssuer example uses the production environment
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
  name: letsencrypt-prod
    email: something@example.com
    http01: {}
      name: letsencrypt-prod
    server: https://acme-v02.api.letsencrypt.org/directory

IMPORTANT: All *-ingress.yaml files use the letsencrypt-stage clusterIssuer. If you are ready for production, change this field in all *-ingress.yaml files to letsencrypt-prod or whatever name you chose for the production. If you choose for Issuer instead of clusterIssuer you also need to change the annotation to certmanager.k8s.io/issuer instead of certmanager.k8s.io/cluster-issuer

Deploying Mailu

All manifests can be found in the mailu subdirectory. All commands below need to be run from this subdirectory


  • All services run in the same namespace, currently mailu-mailserver. So if you want to use a different one, change the namespace value in every file
  • Check the storage-class field in the pvc.yaml file, you can also change the sizes to your liking. Note that you need RWX (read-write-many) and RWO (read-write-once) storageclasses.
  • Check the configmap.yaml and adapt it to your needs. Be sure to check the kubernetes DNS values at the end (if you use a different namespace)
  • Check the *-ingress.yaml files and change it to the domain you want (this is for the kubernetes ingress controller to handle the admin, webmail, webdav and auth connections)


Boot the Mailu components

To start Mailu, run the following commands from the docs/kubernetes/mailu directory

kubectl create -f rbac.yaml
kubectl create -f configmap.yaml
kubectl create -f pvc.yaml
kubectl create -f redis.yaml
kubectl create -f front.yaml
kubectl create -f webmail.yaml
kubectl create -f imap.yaml
kubectl create -f security.yaml
kubectl create -f smtp.yaml
kubectl create -f fetchmail.yaml
kubectl create -f admin.yaml
kubectl create -f webdav.yaml
kubectl create -f admin-ingress.yaml
kubectl create -f webdav-ingress.yaml
kubectl create -f security-ingress.yaml
kubectl create -f webmail-ingress.yaml

Create the first admin account

When the cluster is online you need to create you master user to access https://mail.example.com/admin Enter the main admin pod to create the root account:

kubectl -n mailu-mailserver get po
kubectl -n mailu-mailserver exec -it mailu-admin-.... /bin/sh

And in the pod run the following command. The command uses following entries:

python manage.py admin root example.com password
  • admin Make it an admin user
  • root The first part of the e-mail adres (ROOT@example.com)
  • example.com the domain appendix
  • password the chosen password for the user

Now you should be able to login on the mail account: https://mail.example.com/admin



  • If you are using Dovecot on a shared file system (Glusterfs, NFS,…), you need to create a special override otherwise a lot of indexing errors will occur on your Dovecot pod.
  • I also higher the number of max connections per IP. Now it’s limited to 10.

Enter the dovecot pod:

kubectl -n mailu-mailserver get po
kubectl -n mailu-mailserver exec -it mailu-imap-.... /bin/sh

Create the file overrides/dovecot.conf

vi /overrides/dovecot.conf

And enter following contents:

mail_nfs_index = yes
mail_nfs_storage = yes
mail_fsync = always
mmap_disable = yes

Save and close the file and delete the imap pod to get it recreated.

kubectl -n mailu-mailserver delete po/mailu-imap-....

Wait for the pod to recreate and you’re online! Happy mailing!

Imap login fix

If it seems you’re not able to login using IMAP on your Mailu accounts, check the logs of the imap container to see whether it’s a permissions problem on the database. This problem can be easily fixed by running following commands:

kubectl -n mailu-mailserver exec -it mailu-imap-... /bin/sh
chmod 777 /data/main.db

If the login problem still persists, or more specific, happens now and then and you see some Auth problems on your webmail or mail client, try following steps:

  • Add auth_debug=yes to the /overrides/dovecot.conf file and delete the pod in order to start a new one, which loads the configuration
  • Depending on your network configuration you could still see some allow_nets check failed results in the logs. This means that the IP is not allowed a login
  • If this is happening your network plugin has troubles with the Nginx Ingress Controller using the hostNetwork: true option. Known cases: Flannel and Calico.
  • You should uncomment POD_ADDRESS_RANGE in the configmap.yaml file and add the IP range of your pod network bridge (the range that sadly has failed the allowed_nets test)
  • Delete the IMAP pod and wait for it to restart
kubectl -n mailu-mailserver get po
kubectl -n mailu-mailserver delete po/mailu-imap...

Happy mailing!