Docker-mailserver is fullstack but simple mailserver (smtp, imap, antispam, antivirus, ssl…) using Docker. See the author’s motivations for creating it, here.
While the stack is intended to be run with Docker or Docker Compose, it’s been adapted to Docker Swarm, and to Kubernetes.
This helm chart deploys docker-mailserver into a Kubernetes cluster, in a manner which retains compatibility with the upstream, docker-specific version.
(Created by gh-md-toc)
The chart includes the following features:
There are several ways you might deploy docker-mailserver. The most common would be:
Within a cloud provider, utilizing a load balancer service from the cloud provider (i.e. GKE). This is an expensive option, since typically you’d pay for each individual port (25, 465, 993, etc) which gets load-balanced
Either within a cloud provider, or in a private Kubernetes cluster, behind a non-integrated load-balancer such as haproxy. An example deployment might be something like Funky Penguin’s Poor Man’s K8s Load Balancer, or even a manually configured haproxy instance/pair.
You need helm, obviously.
You need to install cert-manager, and setup issuers (https://docs.cert-manager.io/en/latest/index.html). It’s easy to install using helm (which you have anyway, right?). Cert-manager is what will request and renew SSL certificates required for docker-mailserver to work. The chart will assume that you’ve configured and tested certmanager.
Here are the TL;DR steps for installing cert-manager:
# Install the CustomResourceDefinition resources separately
kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.7/deploy/manifests/00-crds.yaml
# Create the namespace for cert-manager
kubectl create namespace cert-manager
# Label the cert-manager namespace to disable resource validation
kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
# Update your local Helm chart repository cache
helm repo update
# Install the cert-manager Helm chart
helm install \
--name cert-manager \
--namespace cert-manager \
--version v0.7.0 \
jetstack/cert-manager
$ helm install --name docker-mailserver docker-mailserver
(Note: An issues exists for the support of deploying to a custom namespace)
Download the upstream setup.sh to a local folder (ideally the same location you store your custom values.yaml)
Run ./setup.sh
without arguments for a list of full options
Run ./setup.sh <email address>
to create the email addresses in $PWD/config
Example output:
[funkypenguin:~/demo] ./setup.sh email add david@kowalski.elpenguino.net
"docker inspect" requires at least 1 argument.
See 'docker inspect --help'.
Usage: docker inspect [OPTIONS] NAME|ID [NAME|ID...]
Return low-level information on Docker objects
Enter Password:
[funkypenguin:~/demo] %
Example output:
[funkypenguin:~/demo] ./setup.sh config dkim
"docker inspect" requires at least 1 argument.
See 'docker inspect --help'.
Usage: docker inspect [OPTIONS] NAME|ID [NAME|ID...]
Return low-level information on Docker objects
Creating DKIM private key /tmp/docker-mailserver/opendkim/keys/bob.com/mail.private
Creating DKIM KeyTable
Creating DKIM SigningTable
Creating DKIM private key /tmp/docker-mailserver/opendkim/keys/example.com/mail.private
Creating DKIM TrustedHosts
[funkypenguin:~/demo]
If employing HAProxy with RainLoop, use port 10993 for your IMAPS server, as illustrated below:
All configuration values are documented in values.yaml. Check that for references, default values etc. To modify a configuration value for a chart, you can either supply your own values.yaml overriding the default one in the repo:
$ helm upgrade --install path/to/docker-mailserver docker-mailserver --values path/to/custom/values/file.yaml
Or, you can override an individual configuration setting with helm upgrade --set
, specifying each parameter using the --set key=value[,key=value]
argument to helm install
. For example:
$ helm upgrade --install path/to/docker-mailserver docker-mailserver --set pod.dockermailserver.image="your/image:1.0.0"
Most of the values recorded belowe are set to sensible default, butyou’ll definately want to pay attention to at least the following:
Parameter | Description | Default |
---|---|---|
pod.dockermailserver.override_hostname |
The hostname to be presented on SMTP banners | mail.batcave.org |
rainloop.ingress.hosts |
The hostname(s) to be used via your ingress to access RainLoop | rainloop.example.com |
demoMode.enabled |
Start the container with a demo “user@example.com” user (password is “password”) | true |
domains |
List of domains to be served | [] |
ssl.issuer.name |
The name of the cert-manager issuer expected to issue certs | letsencrypt-staging |
ssl.issuer.kind |
Whether the issuer is namespaced (Issuer ) on cluster-wide (ClusterIssuer ) |
ClusterIssuer |
ssl.dnsname |
DNS domain used for DNS01 validation | example.com |
ssl.dns01provider |
The cert-manager DNS01 provider (more details coming) | cloudflare |
The following table lists the configurable parameters of the docker-mailserver chart and their default values.
Parameter | Description | Default |
---|---|---|
image.name |
The name of the container image to use | tvial/docker-mailserver |
image.tag |
The image tag to use (You may prefer “latest” over “v6.1.0”, for example) | release-v6.1.0 |
demoMode.enabled |
Start the container with a demo “user@example.com” user (password is “password”) | true |
haproxy.enabled |
Support HAProxy PROXY protocol on SMTP, IMAP(S), and POP3(S) connections. Provides real source IP instead of load balancer IP | true |
poorMansK8sLb.enabled |
Whether to deploy containers to call webhook for poor-mans-k8s-lb | false |
poorMansK8sLb.webhookUrl |
The webhook to use if poor-mans-k8s-lb is enabled via poorMansK8sLb.enabled |
None |
poorMansK8sLb.webhookSecret |
The secret to use if poor-mans-k8s-lb is enabled via poorMansK8sLb.enabled |
None |
haproxy.trustedNetworks |
The IPs (in space-separated CIDR format) from which to trust inbound HAProxy-enabled connections | "10.0.0.0/8 192.168.0.0/16 172.16.0.0/16" |
spfTestsDisabled |
Disable all SPF-related spam checks (if source IP of inbound connections is a problem, and you’re not using haproxy) | false |
domains |
List of domains to be served | [] |
livenessTests.enabled |
Whether to execute liveness tests by running (arbitrary) commands in the docker-mailserver container. Useful to detect component failure (i.e., clamd dies due to memory pressure) | true |
livenessTests.enabled |
Array of commands to execute in sequence, to determine container health. A non-zero exit of any command is considered a failure | [ "clamscan /tmp/docker-mailserver/TrustedHosts" ] |
pod.dockermailserver.hostNetwork |
Whether the pod should be connected to the “host” network (a primitive solution to ingress NAT problem) | false |
pod.dockermailserver.hostPID |
Not really sure. TBD. | None |
pod.dockermailserver.hostPID |
Not really sure. TBD. | None |
pod.dockermailserver.securityContext.privileged |
Whether to run this pod in “privileged” mode. | false |
service.type |
What scope the service should be exposed in (LoadBalancer/NodePort/ClusterIP) | NodePort |
service.loadBalancer.publicIp |
The public IP to assign to the service (if LoadBalancer) scope selected above | None |
service.loadBalancer.allowedIps |
The IPs allowed to access the sevice, in CIDR format (if LoadBalancer) scope selected above | [ "0.0.0.0/0" ] |
service.nodeport.smtp |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s SMTP port (25) | 30025 |
service.nodeport.pop3 |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s POP3 port (110) | 30110 |
service.nodeport.imap |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s IMAP port (143) | 30143 |
service.nodeport.smtps |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s SMTPS port (465) | 30465 |
service.nodeport.submission |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s submission (SMTP-over-TLS) port (587) | 30587 |
service.nodeport.imaps |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s IMAPS port (993) | 30993 |
service.nodeport.pop3s |
The port exposed on the node the container is running on, which will be forwarded to docker-mailserver’s IMAPS port (993) | 30995 |
deployment.replicas |
How many instances of the container to deploy (only 1 supported currently) | 1 |
resource.requests.cpu |
Initial share of CPU requested per-pod | 1 |
resource.requests.memory |
Initial share of RAM requested per-pod (Initial testing showed clamd would fail due to memory pressure with less than 1.5GB RAM) | 1536Mi |
resource.limits.cpu |
Maximum share of CPU available per-pod | 2 |
resource.limits.memory |
Maximum share of RAM available per-pod | 2048Mi |
persistence.size |
How much space to provision for persistent storage | 10Gi |
persistence.annotations |
Annotations to add to the persistent storage (for example, to support k8s-snapshots) | {} |
ssl.issuer.name |
The name of the cert-manager issuer expected to issue certs | letsencrypt-staging |
ssl.issuer.kind |
Whether the issuer is namespaced (Issuer ) on cluster-wide (ClusterIssuer ) |
ClusterIssuer |
ssl.dnsname |
DNS domain used for DNS01 validation | example.com |
ssl.dns01provider |
The cert-manager DNS01 provider (more details coming) | cloudflare |
There are many environment variables which allow you to customize the behaviour of docker-mailserver. The function of each variable is described at https://github.com/tomav/docker-mailserver#environment-variables
Every variable can be set using values.yaml
, but note that docker-mailserver expects any true/false values to be set as binary numbers (1/0), rather than boolean (true/false). BadThings(tm) will happen if you try to pass an environment variable as “true” when start-mailserver.sh
is expecting a 1 or a 0!
Values you’ll definately want to pay attention to:
Parameter | Description | Default |
---|---|---|
rainloop.ingress.hosts |
The hostname(s) to be used via your ingress to access RainLoop | rainloop.example.com |
Parameter | Description | Default |
---|---|---|
haproxy.deploy_chart |
Whether to deploy the HAProxy Ingress Controller (recomended) | true |
haproxy.controller.kind |
Whether your controller is a DaemonSet or a Deployment |
Deployment |
haproxy.enableStaticPorts |
Whether to enable ports 80 and 443 in addition to the TCP ports we’re using below | false |
haproxy.tcp.25 |
How to forward inbound TCP connections on port 25. Use syntax <namespace>/<service name>:<target port>[<optional proxy protocol>] |
default/docker-mailserver:25::PROXY-V1 |
haproxy.tcp.110 |
How to forward inbound TCP connections on port 110. Use syntax described above. | default/docker-mailserver:25::PROXY-V1 |
haproxy.tcp.143 |
How to forward inbound TCP connections on port 143. Use syntax described above. | default/docker-mailserver:143::PROXY-V1 |
haproxy.tcp.465 |
How to forward inbound TCP connections on port 465. Use syntax described above. PROXY protocol unsupported. | default/docker-mailserver:465 |
haproxy.tcp.587 |
How to forward inbound TCP connections on port 587. Use syntax described above. PROXY protocol unsupported. | default/docker-mailserver:587 |
haproxy.tcp.993 |
How to forward inbound TCP connections on port 993. Use syntax described above. | default/docker-mailserver:993::PROXY-V1 |
haproxy.tcp.995 |
How to forward inbound TCP connections on port 995. Use syntax described above. | default/docker-mailserver:995::PROXY-V1 |
haproxy.service.externalTrafficPolicy |
Used to preserve source IP per this doc | Local |
Unit tests are created for every chart template. Tests are applied to confirm expected behaviour and interaction between various configurations (ie haproxy mode and demo mode)
In addition to tests above, a “snapshot” test is created for each manifest file. This permits a final test per-manifest, which confirms that the generated manifest matches exactly the previous snapshot. If a template change is made, or legit value in values.yaml changes (i.e., the app version) this snapshot test will fail.
If you’re comfortable with the changes to the saved snapshot, then regenerate the snapshots, by running the following from the root of the repo
helm plugin install https://github.com/lrills/helm-unittest
helm unittest helm-chart/docker-mailserver