Skip to content

Commit

Permalink
Improve Docker image
Browse files Browse the repository at this point in the history
* Use TLS filenames same as certbot (see #350).
* Put the Docker-specific maddy.conf in the repo (see #350).
* Set OCI labels for the image in CI
* Move Docker-specific documentation from Docker Hub into docs/
* Add .dockerignore
  • Loading branch information
foxcpp committed Jun 18, 2022
1 parent 9dd34b8 commit 2037b05
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 30 deletions.
4 changes: 4 additions & 0 deletions .dockerignore
@@ -0,0 +1,4 @@
testdata/
cmd/maddy/maddy
maddy
tests/maddy.cover
29 changes: 20 additions & 9 deletions .github/workflows/cicd.yml
Expand Up @@ -85,16 +85,16 @@ jobs:
path: '~/maddy-x86_64-linux-musl.tar.zst'
if-no-files-found: error
docker-builder:
name: "Build Docker image"
needs: build-and-test
name: "Build & push Docker image"
needs: build-and-test # Upload
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
- name: "Set up QEMU"
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- name: Set up Docker Buildx
- name: "Set up Docker Buildx"
id: buildx
uses: docker/setup-buildx-action@v1
- name: "Login to Docker Hub"
Expand All @@ -108,13 +108,24 @@ jobs:
registry: "ghcr.io"
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: "Generate container metadata"
uses: docker/metadata-action@v4
with:
images: |
foxcpp/maddy
ghcr.io/foxcpp/maddy
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
labels: |
org.opencontainers.image.title=Maddy Mail Server
org.opencontainers.image.documentation=https://maddy.email/docker/
org.opencontainers.image.url=https://maddy.email
- name: "Build and push"
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64/v8
platforms: linux/amd64,linux/arm64
push: true
tags: |
foxcpp/maddy:${{ github.ref_name }}
ghcr.io/foxcpp/maddy:${{ github.ref_name }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
1 change: 1 addition & 0 deletions .mkdocs.yml
Expand Up @@ -19,6 +19,7 @@ nav:
- multiple-domains.md
- upgrading.md
- seclevels.md
- docker.md
- Reference manual:
- reference/modules.md
- reference/global-config.md
Expand Down
31 changes: 14 additions & 17 deletions Dockerfile
@@ -1,33 +1,30 @@
FROM golang:1.17-alpine AS build-env

RUN set -ex ;\
apk upgrade --no-cache --available ;\
apk add --no-cache bash git build-base
RUN set -ex && \
apk upgrade --no-cache --available && \
apk add --no-cache build-base

WORKDIR /maddy
ADD go.mod go.sum ./
ENV LDFLAGS -static

COPY go.mod go.sum ./
RUN go mod download
ADD . ./
RUN mkdir -p /pkg/data
COPY maddy.conf /pkg/data/maddy.conf
# Monkey-patch config to use environment.
RUN sed -Ei 's!\$\(hostname\) = .+!$(hostname) = {env:MADDY_HOSTNAME}!' /pkg/data/maddy.conf
RUN sed -Ei 's!\$\(primary_domain\) = .+!$(primary_domain) = {env:MADDY_DOMAIN}!' /pkg/data/maddy.conf
RUN sed -Ei 's!^tls .+!tls file /data/tls_cert.pem /data/tls_key.pem!' /pkg/data/maddy.conf

RUN ./build.sh --builddir /tmp --destdir /pkg/ --tags docker build install
COPY . ./
RUN mkdir -p /pkg/data && \
cp maddy.conf.docker /pkg/data/maddy.conf && \
./build.sh --builddir /tmp --destdir /pkg/ --tags docker build install

FROM alpine:3.15.0
FROM alpine:3.16.0
LABEL maintainer="fox.cpp@disroot.org"
LABEL org.opencontainers.image.source=https://github.com/foxcpp/maddy

RUN set -ex ;\
apk upgrade --no-cache --available ;\
RUN set -ex && \
apk upgrade --no-cache --available && \
apk --no-cache add ca-certificates
COPY --from=build-env /pkg/data/maddy.conf /data/maddy.conf
COPY --from=build-env /pkg/usr/local/bin/maddy /pkg/usr/local/bin/maddyctl /bin/

EXPOSE 25 143 993 587 465
VOLUME ["/data"]
ENTRYPOINT ["/bin/maddy", "-config", "/data/maddy.conf", "run"]
ENTRYPOINT ["/bin/maddy", "-config", "/data/maddy.conf"]
CMD ["run"]
75 changes: 75 additions & 0 deletions docs/docker.md
@@ -0,0 +1,75 @@
# Docker

Official Docker image is available from Docker Hub.

It expects configuration file to be available at /data/maddy.conf.

If /data is a Docker volume, then default configuration will be placed there
automatically. If it is used, then MADDY_HOSTNAME, MADDY_DOMAIN environment
variables control the host name and primary domain for the server. TLS
certificate should be placed in /data/tls/fullchain.pem, private key in
/data/tls/privkey.pem

DKIM keys are generated in /data/dkim_keys directory.

## Image tags

- `latest` - A latest stable release. May contain breaking changes.
- `X.Y` - A specific feature branch, it is recommended to use these tags to
receive bugfixes without the risk of feature-related regressions or breaking
changes.
- `X.Y.Z` - A specific stable release

## Ports

All standard ports, as described in maddy docs.

- `25` - SMTP inbound port.
- `465`, `587` - SMTP Submission ports
- `993`, `143` - IMAP4 ports

## Volumes

`/data` - maddy state directory. Databases, queues, etc are stored here. You
might want to mount a named volume there. The main configuration file is stored
here too (`/data/maddy.conf`).

## Management utility

To run management commands, create a temporary container with the same
/data directory and put the command after the image name, like this:

```
docker run --rm -it -v maddydata:/data foxcpp/maddy:0.6.0 creds create foxcpp@maddy.test
docker run --rm -it -v maddydata:/data foxcpp/maddy:0.6.0 imap-acct create foxcpp@maddy.test
```

Use the same image version as the running server. Things may break badly
otherwise.

Note that, if you modify messages using maddyctl while the server is running -
you must ensure that /tmp from the server is accessible for the management
command. One way to it is to run it using `docker exec` instead of `docker run`:
```
docker exec -it container_name_here maddy creds create foxcpp@maddy.test
```

## TL;DR

```
docker volume create maddydata
docker run \
--name maddy \
-e MADDY_HOSTNAME=mx.maddy.test \
-e MADDY_DOMAIN=maddy.test \
-v maddydata:/data \
-p 25:25 \
-p 143:143 \
-p 587:587 \
-p 993:993 \
foxcpp/maddy:0.6
```

It will fail on first startup. Copy TLS certificate to /data/tls/fullchain.pem
and key to /data/tls/privkey.pem. Run the server again. Finish DNS configuration
(DKIM keys, etc) as described in [tutorials/setting-up/](tutorials/setting-up/).
5 changes: 1 addition & 4 deletions maddy.conf
@@ -1,12 +1,9 @@
## Maddy Mail Server - default configuration file (2021-08-16)
## Maddy Mail Server - default configuration file (2022-06-18)
# Suitable for small-scale deployments. Uses its own format for local users DB,
# should be managed via maddyctl utility.
#
# See tutorials at https://maddy.email for guidance on typical
# configuration changes.
#
# See manual pages (also available at https://maddy.email) for reference
# documentation.

# ----------------------------------------------------------------------------
# Base variables
Expand Down
182 changes: 182 additions & 0 deletions maddy.conf.docker
@@ -0,0 +1,182 @@
## Maddy Mail Server - default configuration file (2022-06-18)
## This is the copy of maddy.conf with changes necessary to run it in Docker.
# Suitable for small-scale deployments. Uses its own format for local users DB,
# should be managed via maddyctl utility.
#
# See tutorials at https://maddy.email for guidance on typical
# configuration changes.

# ----------------------------------------------------------------------------
# Base variables

$(hostname) = {env:MADDY_HOSTNAME}
$(primary_domain) = {env:MADDY_DOMAIN}
$(local_domains) = $(primary_domain)

tls file /data/tls/fullchain.pem /data/tls/privkey.pem

# ----------------------------------------------------------------------------
# Local storage & authentication

# pass_table provides local hashed passwords storage for authentication of
# users. It can be configured to use any "table" module, in default
# configuration a table in SQLite DB is used.
# Table can be replaced to use e.g. a file for passwords. Or pass_table module
# can be replaced altogether to use some external source of credentials (e.g.
# PAM, /etc/shadow file).
#
# If table module supports it (sql_table does) - credentials can be managed
# using 'maddyctl creds' command.

auth.pass_table local_authdb {
table sql_table {
driver sqlite3
dsn credentials.db
table_name passwords
}
}

# imapsql module stores all indexes and metadata necessary for IMAP using a
# relational database. It is used by IMAP endpoint for mailbox access and
# also by SMTP & Submission endpoints for delivery of local messages.
#
# IMAP accounts, mailboxes and all message metadata can be inspected using
# imap-* subcommands of maddyctl utility.

storage.imapsql local_mailboxes {
driver sqlite3
dsn imapsql.db
}

# ----------------------------------------------------------------------------
# SMTP endpoints + message routing

hostname $(hostname)

table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
optional_step file /etc/maddy/aliases
}

msgpipeline local_routing {
# Insert handling for special-purpose local domains here.
# e.g.
# destination lists.example.org {
# deliver_to lmtp tcp://127.0.0.1:8024
# }

destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
}

deliver_to &local_mailboxes
}

default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}

smtp tcp://0.0.0.0:25 {
limits {
# Up to 20 msgs/sec across max. 10 SMTP connections.
all rate 20 1s
all concurrency 10
}

dmarc yes
check {
require_mx_record
dkim
spf
}

source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}

submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
limits {
# Up to 50 msgs/sec across any amount of SMTP connections.
all rate 50 1s
}

auth &local_authdb

source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}

destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) $(local_domains) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}

target.remote outbound_delivery {
limits {
# Up to 20 msgs/sec across max. 10 SMTP connections
# for each recipient domain.
destination rate 20 1s
destination concurrency 10
}
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level encrypted
min_mx_level none
}
}
}

target.queue remote_queue {
target &outbound_delivery

autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}

# ----------------------------------------------------------------------------
# IMAP endpoints

imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
auth &local_authdb
storage &local_mailboxes
}

0 comments on commit 2037b05

Please sign in to comment.