Go to file
Brennan Kinney 7ca056852f
tests(feat): Complete rewrite of letsencrypt tests (#2286)
* chore: Normalize container setup

Easier to grok what is different between configurations.

- Container name usage replaced with variable
- Volumes defined earlier and redeclared when relevant (only real difference is `VOLUME_LETSENCRYPT`)
- Contextual comment about the `acme.json` copy.
- Quoting `SSL_TYPE`, `SSL_DOMAIN` and `-h` values for syntax highlighting.
- Moved `-t` and `${NAME}` to separate line.
- Consistent indentation.

* chore: DRY test logic

Extracts out repeated test logic into methods

* chore: Scope configs to individual test cases (1/3)

- Preparation step for shifting out the container configs to their own scoped test cases. Split into multiple commits to ease reviewing by diffs for this change.
- Re-arrange the hostname and domain configs to match the expected order of the new test cases.
- Shuffle the hostname and domainname grouped tests into tests per container config scope.
- Collapse the `acme.json` test cases into single test case.

* chore: Scope configs to individual test cases (2/3)

- Shifts the hostname and domainname container configs into their respective scoped test cases.
- Moving the `acme.json` container config produces a less favorable diff, so is deferred to a follow-up commit.
- Test cases updated to refer to their `${CONTAINER_NAME}` var instead of the hard-coded string name.

* chore: Scope configs to individual test cases (3/3)

Final commit to shift out the container configs.

- Common vars are exported in `setup_file()` for the test cases to use without needing to repeat the declaration in each test case.
- `teardown_file()` shifts container removal at end of scoped test case.

* chore: Adapt to `common_container_setup` template

- `CONTAINER_NAME` becomes `TEST_NAME` (`common.bash` helper via `init_with_defaults`).
- `docker run ...` and related configuration is now outsourced to the `common.bash` helper, only extra args that the default template does not cover are defined in the test case.
- `TARGET_DOMAIN`establishes the domain folder name for `/etc/letsencrypt/live`.
- `_should*` methods no longer manage a `CONTAINER_NAME` arg, instead using the `TEST_NAME` global that should be valid as test is run as a sequence of test cases.
- `PRIVATE_CONFIG` and the `private_config_path ...` are now using the global `TEST_TMP_CONFIG` initialized at the start of each test case, slightly different as not locally defined/scoped like `PRIVATE_CONFIG` would be within the test case, hence the explicit choice of a different name for context.

* chore: Minor tweaks

- Test case comment descriptions.
- DRY: `docker rm -f` lines moved to `teardown()`
- Use `wait_for_service` helper instead of checking the `changedetector` script itself is running.
- There is a startup delay before the `changedetector` begins monitoring, wait until it ready event is logged.
- Added a helper to query logs for a service (useful later).
- `/bin/sh` commands reduced to `sh`.
- Change the config check to match and compare output, not number of lines returned. Provides better failure output by bats to debug against.

* chore: Add more test functions for `acme.json`

This just extracts out existing logic from the test case to functions to make the test case itself more readable/terse.

* chore: Housekeeping

No changes, just moving logic around and grouping into inline functions, with some added comments.

* chore: Switch to `example.test` certs

This also required copying the source files to match the expected letsencrypt file structure expected in the test/container usage.

* chore: Delete `test/config/letsencrypt/`

No longer necessary, using the `example.test/` certs instead.

These letsencrypt certs weren't for the domains they were used for, and of course long expired.

* chore: Housekeeping

Add more maintainer comments, rename some functions.

* tests: Expand `acme.json` extraction coverage

Finally able to add more test coverage! :)

- Two new methods to validate expected success/failure of extraction for a given FQDN.
- Added an RSA test prior to the wildcard to test a renewal simulation (just with different cert type).
- Added extra method to make sure we're detecting multiple successful change events, not just a previous logged success (false positive).

* tests: Refactor the negotiate_tls functionality

Covers all ports (except POP) and correctly tests against expected verification status with new `example.test` certs.

The `FQDN` var will be put to use in a follow-up commit.

* tests: Verify the certs contain the expected FQDNs

* chore: Extract TLS test methods into a separate helper script

Can be useful for other TLS tests to utilize.

* chore: Housekeeping

* chore: Fix test typo

There was a mismatch between the output and expected output between these two files "find key for" and "find key & cert for". Changed to "find key and/or cert for" to make the warning more clear that it's issued for either or both failure conditions.

Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2021-11-16 16:00:16 +01:00
.github Revise workflow for stalled objects (#2301) 2021-11-15 21:00:25 +01:00
config example added (#1922) 2021-04-20 08:50:24 +02:00
docs ci(docs): Fail when build aborts from broken links (#2266) 2021-10-31 09:13:18 +13:00
target tests(feat): Complete rewrite of letsencrypt tests (#2286) 2021-11-16 16:00:16 +01:00
test tests(feat): Complete rewrite of letsencrypt tests (#2286) 2021-11-16 16:00:16 +01:00
.all-contributorsrc Update contributors (#2143) 2021-08-28 15:23:11 +02:00
.dockerignore Update check (#1951) 2021-05-19 21:18:06 +02:00
.editorconfig cleaned up >/dev/nulls in Dockerfile and replaced em dashes with normal dashes (#2024) 2021-06-08 13:20:20 +12:00
.gitignore MacOS linting & testing support + docs (#2001) 2021-06-07 14:58:34 +02:00
.gitmodules update bats to latest version 2019-08-05 21:40:09 +02:00
CHANGELOG.md release: version v10.2.0 (#2217) 2021-10-07 10:44:07 +02:00
CODE_OF_CONDUCT.md docs(fix): Update wiki references to the new docs url 2021-03-25 11:49:24 +13:00
CONTRIBUTORS.md docs(CONTRIBUTORS): update contributors (#2276) 2021-11-01 16:30:48 +13:00
docker-compose.yml chore: Remove version key from docker-compose.yml (#2271) 2021-10-30 14:12:47 +02:00
Dockerfile Improve VIRUSMAILS_DELETE_DELAY usage (#2281) 2021-11-01 22:13:14 +01:00
LICENSE Final Migration Step (#6) 2021-01-16 10:16:05 +01:00
mailserver.env Fix quote mistake made in #2236 (#2237) 2021-10-04 08:46:33 +13:00
Makefile ci(tests): Add timing measurement to output (#2269) 2021-10-30 23:57:21 +13:00
README.md Update README to reflect changes of v10.2.0 (#2234) 2021-10-03 15:29:44 +02:00
setup.sh fix(setup.sh): Add legacy config path check (#2250) 2021-10-16 11:27:17 +02:00
VERSION release: version v10.2.0 (#2217) 2021-10-07 10:44:07 +02:00

Docker Mailserver

ci::status docker::pulls documentation::badge

A fullstack but simple mail-server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.). Only configuration files, no SQL database. Keep it simple and versioned. Easy to deploy and upgrade. Documentation via MkDocs. Why this image was created.

If you have issues, read the full README and the documentation for your version (default is edge) first before opening an issue. The issue tracker is for issues, not for personal support.

  1. Included Services
  2. Issues and Contributing
  3. Requirements
  4. Usage
  5. Examples
  6. Environment Variables
  7. Documentation
  8. Release Notes

Included Services

Requirements

Recommended:

  • 1 Core
  • 2GB RAM
  • Swap enabled for the container

Minimum:

  • 1 vCore
  • 512MB RAM

Note: You'll need to deactivate some services like ClamAV to be able to run on a host with 512MB of RAM. Even with 1G RAM you may run into problems without swap, see FAQ.

Usage

Available Images / Tags - Tagging Convention

CI/CD will automatically build, test and push new images to container registries. Currently, the following registries are supported:

All workflows are using the tagging convention listed below. It is subsequently applied to all images.

Event Ref Image Tags
push refs/heads/master edge
push tag refs/tags/[v]1.2.3 1.2.3, 1.2, 1, latest

Get the tools

Since Docker Mailserver v10.2.0, setup.sh functionality is included within the Docker image. The external convenience script is no longer required if you prefer using docker exec <CONTAINER NAME> setup <COMMAND> instead.

Note: If you're using Docker or Docker Compose and are new to docker-mailserver, it is recommended to use the script setup.sh for convenience.

DMS_GITHUB_URL='https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master'
wget "${DMS_GITHUB_URL}/docker-compose.yml"
wget "${DMS_GITHUB_URL}/mailserver.env"
wget "${DMS_GITHUB_URL}/setup.sh"

chmod a+x ./setup.sh
./setup.sh help

If no docker-mailserver container is running, any ./setup.sh command will check online for the :latest image tag (the current stable release), performing a pull if necessary followed by running the command in a temporary container.

setup.sh for docker-mailserver version v10.1.x and below

If you're using docker-mailserver version v10.1.x or below, you will need to get setup.sh with a specific version. Substitute <VERSION> with the docker-mailserver release version you're using: wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/<VERSION>/setup.sh.

Create a docker-compose environment

  1. Install the latest docker-compose
  2. Edit docker-compose.yml to your liking
    • substitute mail (hostname) and example.com (domainname) according to your FQDN
    • if you want to use SELinux for the ./docker-data/dms/config/:/tmp/docker-mailserver/ mount, append -z or -Z
  3. Configure the mailserver container to your liking by editing mailserver.env (Documentation), but keep in mind this .env file:
    • only basic VAR=VAL is supported (do not quote your values!)
    • variable substitution is not supported (e.g. 🚫 OVERRIDE_HOSTNAME=$HOSTNAME.$DOMAINNAME 🚫)

Get up and running

First Things First

Use docker-compose up / down, not docker-compose start / stop. Otherwise, the container is not properly destroyed and you may experience problems during startup because of inconsistent state.

You are able to get a full overview of how the configuration works by either running:

  1. ./setup.sh help which includes the options of setup.sh.
  2. docker run --rm docker.io/mailserver/docker-mailserver:latest setup help which provides you with all the information on configuration provided "inside" the container itself.

Starting for the first time

On first start, you will likely see an error stating that there are no mail accounts and the container will exit. You must now do one of two things:

  1. Use setup.sh to help you: ./setup.sh email add <user@domain> <password>. You may need the -c option to provide the local path for persisting configuration (a directory that mounts to /tmp/docker-mailserver inside the container). This will spin up a new container, mount your configuration volume, and create your first account.
  2. Execute the complete command yourself: docker run --rm -v "${PWD}/docker-data/dms/config/:/tmp/docker-mailserver/" docker.io/mailserver/docker-mailserver setup email add <user@domain> <password>. Make sure to mount the correct configuration directory.

You can then proceed by creating the postmaster alias and by creating DKIM keys.

docker-compose up -d mailserver

# you may add some more users
# for SELinux, use -Z
./setup.sh [-Z] email add <user@domain> [<password>]

# and configure aliases, DKIM and more
./setup.sh [-Z] alias add postmaster@<domain> <user@domain>
./setup.sh [-Z] config dkim

In case you're using LDAP, the setup looks a bit different as you do not add user accounts directly. Postfix doesn't know your domain(s) and you need to provide it when configuring DKIM:

./setup.sh config dkim domain '<domain.tld>[,<domain2.tld>]'

If you want to see detailed usage information, run ./setup.sh config dkim help.

Miscellaneous

DNS - DKIM

When keys are generated, you can configure your DNS server by just pasting the content of config/opendkim/keys/domain.tld/mail.txt to set up DKIM. See the documentation for more details.

Custom User Changes & Patches

If you'd like to change, patch or alter files or behavior of docker-mailserver, you can use a script. See the documentation for a detailed explanation.

Updating docker-mailserver

Make sure to read the CHANGELOG before updating to new versions, to be prepared for possible breaking changes.

docker-compose pull
docker-compose down
docker-compose up -d mailserver

You should see the new version number on startup, for example: [ TASKLOG ] Welcome to docker-mailserver 10.1.2.

You're done! And don't forget to have a look at the remaining functions of the setup.sh script with ./setup.sh help.

Supported Operating Systems

We are currently providing support for Linux. Windows is not supported and is known to cause problems. Similarly, macOS is not officially supported - but you may get it to work there. In the end, Linux should be your preferred operating system for this image, especially when using this mail-server in production.

Bare Domains

If you want to use a bare domain (hostname == domainname), see FAQ.

Support for Multiple Domains

docker-mailserver supports multiple domains out of the box, so you can do this:

./setup.sh email add user1@docker.example.com
./setup.sh email add user1@mail.example.de
./setup.sh email add user1@server.example.org

SPF/Forwarding Problems

If you got any problems with SPF and/or forwarding mails, give SRS a try. You enable SRS by setting ENABLE_SRS=1. See the variable description for further information.

Ports

See the documentation for further details and best practice advice, especially regarding security concerns.

Mailboxes (aka IMAP Folders)

INBOX is setup by default with the special IMAP folders Drafts, Sent, Junk and Trash. You can learn how to modify or add your own folders (including additional special folders like Archive) by visiting our docs page Customizing IMAP Folders for more information.

Examples

With Relevant Environmental Variables

This example provides you only with a basic example of what a minimal setup could look like. We strongly recommend that you go through the configuration file yourself and adjust everything to your needs. The default docker-compose.yml can be used for the purpose out-of-the-box, see the usage section.

version: '3.8'

services:
  mailserver:
    image: docker.io/mailserver/docker-mailserver:latest
    container_name: mailserver
    hostname: mail
    domainname: example.com
    ports:
      - "25:25"
      - "143:143"
      - "587:587"
      - "993:993"
    volumes:
      - ./docker-data/dms/mail-data/:/var/mail/
      - ./docker-data/dms/mail-state/:/var/mail-state/
      - ./docker-data/dms/mail-logs/:/var/log/mail/
      - ./docker-data/dms/config/:/tmp/docker-mailserver/
      - /etc/localtime:/etc/localtime:ro
    environment:
      - ENABLE_SPAMASSASSIN=1
      - SPAMASSASSIN_SPAM_TO_INBOX=1
      - ENABLE_CLAMAV=1
      - ENABLE_FAIL2BAN=1
      - ENABLE_POSTGREY=1
      - ENABLE_SASLAUTHD=0
      - ONE_DIR=1
      - DMS_DEBUG=0
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE
    restart: always

LDAP setup

version: '3.8'

services:
  mailserver:
    image: docker.io/mailserver/docker-mailserver:latest
    container_name: mailserver
    hostname: mail
    domainname: example.com
    ports:
      - "25:25"
      - "143:143"
      - "587:587"
      - "993:993"
    volumes:
      - ./docker-data/dms/mail-data/:/var/mail/
      - ./docker-data/dms/mail-state/:/var/mail-state/
      - ./docker-data/dms/mail-logs/:/var/log/mail/
      - ./docker-data/dms/config/:/tmp/docker-mailserver/
      - /etc/localtime:/etc/localtime:ro
    environment:
      - ENABLE_SPAMASSASSIN=1
      - SPAMASSASSIN_SPAM_TO_INBOX=1
      - ENABLE_CLAMAV=1
      - ENABLE_FAIL2BAN=1
      - ENABLE_POSTGREY=1
      - ONE_DIR=1
      - DMS_DEBUG=0
      - ENABLE_LDAP=1
      - LDAP_SERVER_HOST=ldap # your ldap container/IP/ServerName
      - LDAP_SEARCH_BASE=ou=people,dc=localhost,dc=localdomain
      - LDAP_BIND_DN=cn=admin,dc=localhost,dc=localdomain
      - LDAP_BIND_PW=admin
      - LDAP_QUERY_FILTER_USER=(&(mail=%s)(mailEnabled=TRUE))
      - LDAP_QUERY_FILTER_GROUP=(&(mailGroupMember=%s)(mailEnabled=TRUE))
      - LDAP_QUERY_FILTER_ALIAS=(|(&(mailAlias=%s)(objectClass=PostfixBookMailForward))(&(mailAlias=%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE)))
      - LDAP_QUERY_FILTER_DOMAIN=(|(&(mail=*@%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE))(&(mailGroupMember=*@%s)(objectClass=PostfixBookMailAccount)(mailEnabled=TRUE))(&(mailalias=*@%s)(objectClass=PostfixBookMailForward)))
      - DOVECOT_PASS_FILTER=(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))
      - DOVECOT_USER_FILTER=(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))
      - ENABLE_SASLAUTHD=1
      - SASLAUTHD_MECHANISMS=ldap
      - SASLAUTHD_LDAP_SERVER=ldap
      - SASLAUTHD_LDAP_BIND_DN=cn=admin,dc=localhost,dc=localdomain
      - SASLAUTHD_LDAP_PASSWORD=admin
      - SASLAUTHD_LDAP_SEARCH_BASE=ou=people,dc=localhost,dc=localdomain
      - SASLAUTHD_LDAP_FILTER=(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%U))
      - POSTMASTER_ADDRESS=postmaster@localhost.localdomain
      - POSTFIX_MESSAGE_SIZE_LIMIT=100000000
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE
    restart: always