mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-16 14:27:01 +00:00
docker: Add Dockerfile for running chasquid+dovecot+letsencrypt
This patch adds a new docker directory, which contains a Dockerfile plus some additional configuration for creating a container that runs chasquid+dovecot+letsencrypt. It also updates the gitlab CI pipeline to automatically build and publish an image on each commit. This is experimental and likely to break.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
|
||||
stages:
|
||||
- test
|
||||
- docker_image
|
||||
|
||||
integration_test:
|
||||
stage: test
|
||||
@@ -12,3 +13,14 @@ integration_test:
|
||||
- docker build -t chasquid-test -f test/Dockerfile .
|
||||
- docker run chasquid-test env
|
||||
- docker run chasquid-test make test
|
||||
|
||||
image_build:
|
||||
stage: docker_image
|
||||
image: docker:stable
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- docker info
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME -f docker/Dockerfile .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME
|
||||
|
||||
@@ -25,6 +25,7 @@ nav:
|
||||
- hooks.md
|
||||
- dovecot.md
|
||||
- dkim.md
|
||||
- docker.md
|
||||
- flow.md
|
||||
- monitoring.md
|
||||
- sec-levels.md
|
||||
|
||||
84
docker/Dockerfile
Normal file
84
docker/Dockerfile
Normal file
@@ -0,0 +1,84 @@
|
||||
# Docker file for creating a container that will run chasquid and Dovecot.
|
||||
#
|
||||
# THIS IS EXPERIMENTAL AND LIKELY TO CHANGE.
|
||||
#
|
||||
# This is not recommended for serious installations, you're probably better
|
||||
# off following the documentation and setting the server up manually.
|
||||
#
|
||||
# See the README.md file for more details.
|
||||
|
||||
# Build the binaries.
|
||||
FROM golang:latest as build
|
||||
WORKDIR /go/src/blitiri.com.ar/go/chasquid
|
||||
COPY . .
|
||||
RUN go get -d ./...
|
||||
RUN go install ./...
|
||||
|
||||
# Create the image.
|
||||
FROM debian:stable
|
||||
|
||||
# Make debconf/frontend non-interactive, to avoid distracting output about the
|
||||
# lack of $TERM.
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# Install the packages we need.
|
||||
# This includes chasquid, which sets up good defaults.
|
||||
RUN apt-get update -q
|
||||
RUN apt-get install -y -q \
|
||||
chasquid \
|
||||
dovecot-lmtpd dovecot-imapd dovecot-pop3d \
|
||||
dovecot-sieve dovecot-managesieved \
|
||||
acl sudo certbot
|
||||
|
||||
# Copy the binaries. This overrides the debian packages with the ones we just
|
||||
# built above.
|
||||
COPY --from=build /go/bin/chasquid /usr/bin/
|
||||
COPY --from=build /go/bin/chasquid-util /usr/bin/
|
||||
COPY --from=build /go/bin/smtp-check /usr/bin/
|
||||
COPY --from=build /go/bin/mda-lmtp /usr/bin/
|
||||
|
||||
# Let chasquid bind privileged ports, so we can run it as its own user.
|
||||
RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/chasquid
|
||||
|
||||
# Copy docker-specific configurations.
|
||||
COPY docker/dovecot.conf /etc/dovecot/dovecot.conf
|
||||
COPY docker/chasquid.conf /etc/chasquid/chasquid.conf
|
||||
|
||||
# Copy utility scripts.
|
||||
COPY docker/add-user.sh /
|
||||
COPY docker/entrypoint.sh /
|
||||
|
||||
# chasquid: SMTP, submission, submission+tls.
|
||||
EXPOSE 25 465 587
|
||||
|
||||
# dovecot: POP3s, IMAPs, managesieve.
|
||||
EXPOSE 993 995 4190
|
||||
|
||||
# http for letsencrypt/certbot.
|
||||
EXPOSE 80 443
|
||||
|
||||
# Store emails and chasquid databases in an external volume, to be mounted at
|
||||
# /data, so they're independent from the image itself.
|
||||
VOLUME /data
|
||||
|
||||
# Put some directories where we expect persistent user data into /data.
|
||||
RUN rmdir /etc/chasquid/domains/
|
||||
RUN ln -sf /data/chasquid/domains/ /etc/chasquid/domains
|
||||
RUN rm -rf /etc/letsencrypt/
|
||||
RUN ln -sf /data/letsencrypt/ /etc/letsencrypt
|
||||
|
||||
# Give the chasquid user access to the necessary configuration.
|
||||
RUN setfacl -R -m u:chasquid:rX /etc/chasquid/
|
||||
RUN mv /etc/chasquid/certs/ /etc/chasquid/certs-orig
|
||||
RUN ln -s /etc/letsencrypt/live/ /etc/chasquid/certs
|
||||
|
||||
|
||||
# NOTE: Set AUTO_CERTS="example.com example.org" to automatically obtain and
|
||||
# renew certificates upon startup, via Letsencrypt. You're agreeing to their
|
||||
# ToS by setting this variable, so please review them carefully.
|
||||
# CERTS_EMAIL should be set to your email address so letsencrypt can send you
|
||||
# critical notifications.
|
||||
|
||||
# Custom entry point that does some configuration checks and ensures
|
||||
# letsencrypt is properly set up.
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
84
docker/README.md
Normal file
84
docker/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
# Docker
|
||||
|
||||
chasquid comes with a Dockerfile to create a container running [chasquid],
|
||||
[dovecot], and managed certificates with [Let's Encrypt].
|
||||
|
||||
**IT IS EXPERIMENTAL AND LIKELY TO BREAK**
|
||||
|
||||
The more traditional setup is **highly recommended**, see the
|
||||
[how-to](howto.md) documentation for more details.
|
||||
|
||||
[chasquid]: https://blitiri.com.ar/p/chasquid
|
||||
[dovecot]: https://dovecot.org
|
||||
[Let's Encrypt]: https://letsencrypt.org
|
||||
|
||||
|
||||
## Images
|
||||
|
||||
There are [pre-built images at gitlab
|
||||
registry](https://gitlab.com/albertito/chasquid/container_registry). They are
|
||||
automatically built, and tagged with the corresponding branch name. Use the
|
||||
*master* tag for a stable version.
|
||||
|
||||
If, instead, you want to build the image yourself, just run:
|
||||
|
||||
```sh
|
||||
$ docker build -t chasquid -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
First, pull the image into your target machine:
|
||||
|
||||
```sh
|
||||
$ docker pull registry.gitlab.com/albertito/chasquid:master
|
||||
```
|
||||
|
||||
You will need a data volume to store persistent data, outside the image. This
|
||||
will contain the mailboxes, user databases, etc.
|
||||
|
||||
```sh
|
||||
$ docker volume create chasquid-data
|
||||
```
|
||||
|
||||
To add your first user to the image:
|
||||
|
||||
```
|
||||
$ docker run \
|
||||
--mount source=chasquid-data,target=/data \
|
||||
-it --entrypoint=/add-user.sh \
|
||||
registry.gitlab.com/albertito/chasquid:master
|
||||
Email (full user@domain format): pepe@example.com
|
||||
Password:
|
||||
pepe@example.com added to /data/dovecot/users
|
||||
```
|
||||
|
||||
Upon startup, the image will obtain a TLS certificate for you using [Let's
|
||||
Encrypt](https://letsencrypt.com/). You need to tell it the domain(s) to get a
|
||||
certificate from by setting the `AUTO_CERTS` variable.
|
||||
|
||||
Because certificates expire, you should restart the container every week or
|
||||
so. Certificates will be renewed automatically upon startup if needed.
|
||||
|
||||
In order for chasquid to get access to the source IP address, you will need to
|
||||
use host networking, or create a custom docker network that does IP forwarding
|
||||
and not proxying.
|
||||
|
||||
Finally, start the container:
|
||||
|
||||
```sh
|
||||
$ docker run -e AUTO_CERTS=mail.yourdomain.com \
|
||||
--mount source=chasquid-data,target=/data \
|
||||
--network host \
|
||||
registry.gitlab.com/albertito/chasquid:master
|
||||
```
|
||||
|
||||
|
||||
## Debugging
|
||||
|
||||
To get a shell on the running container for debugging, you can use `docker ps`
|
||||
to find the container ID, and then `docker exec -it CONTAINERID /bin/bash` to
|
||||
open a shell on the running container.
|
||||
|
||||
43
docker/add-user.sh
Executable file
43
docker/add-user.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Creates a user. If it exists, updates the password.
|
||||
#
|
||||
# Note this is not robust, it's only for convenience on extremely simple
|
||||
# setups.
|
||||
|
||||
set -e
|
||||
|
||||
read -p "Email (full user@domain format): " EMAIL
|
||||
|
||||
if ! echo "${EMAIL}" | grep -q @; then
|
||||
echo "Error: email should have '@'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
read -p "Password: " -s PASSWORD
|
||||
echo
|
||||
|
||||
DOMAIN=$(echo echo "${EMAIL}" | cut -d '@' -f 2)
|
||||
|
||||
|
||||
# If the domain doesn't exist in chasquid's config, create it.
|
||||
mkdir -p "/data/chasquid/domains/${DOMAIN}/"
|
||||
|
||||
|
||||
# Encrypt password.
|
||||
ENCPASS=$(doveadm pw -u "${EMAIL}" -p "${PASSWORD}")
|
||||
|
||||
# Edit dovecot users: remove user if it exits.
|
||||
mkdir -p /data/dovecot
|
||||
if grep -q "^${EMAIL}:" /data/dovecot/users; then
|
||||
cp /data/dovecot/users /data/dovecot/users.old
|
||||
cat /data/dovecot/users.old | grep -v "^${EMAIL}:" \
|
||||
> /data/dovecot/users
|
||||
fi
|
||||
|
||||
# Edit dovecot users: add user.
|
||||
echo "${EMAIL}:${ENCPASS}::::" >> /data/dovecot/users
|
||||
|
||||
echo "${EMAIL} added to /data/dovecot/users"
|
||||
|
||||
26
docker/chasquid.conf
Normal file
26
docker/chasquid.conf
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
# Listening addresses.
|
||||
smtp_address: ":25"
|
||||
submission_address: ":587"
|
||||
submission_over_tls_address: ":465"
|
||||
|
||||
# Monitoring HTTP server only bound to localhost, just in case.
|
||||
monitoring_address: "127.0.0.1:1099"
|
||||
|
||||
# Auth against dovecot.
|
||||
dovecot_auth: true
|
||||
|
||||
# Use mda-lmtp to talk to dovecot.
|
||||
mail_delivery_agent_bin: "/usr/bin/mda-lmtp"
|
||||
mail_delivery_agent_args: "--addr"
|
||||
mail_delivery_agent_args: "/run/dovecot/lmtp"
|
||||
mail_delivery_agent_args: "-f"
|
||||
mail_delivery_agent_args: "%from%"
|
||||
mail_delivery_agent_args: "-d"
|
||||
mail_delivery_agent_args: "%to%"
|
||||
|
||||
# Store data in the container volume.
|
||||
data_dir: "/data/chasquid/data"
|
||||
|
||||
# Mail log to the container volume.
|
||||
mail_log_path: "/data/chasquid/mail.log"
|
||||
134
docker/dovecot.conf
Normal file
134
docker/dovecot.conf
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
#
|
||||
# Logging
|
||||
#
|
||||
log_path = /data/dovecot/dovecot.log
|
||||
|
||||
#
|
||||
# Email storage
|
||||
#
|
||||
|
||||
# Store emails in /data/mail/home/domain/user, which will be inside the
|
||||
# container's volume.
|
||||
mail_home = /data/mail/home/%d/%n
|
||||
|
||||
# Use Dovecot's native format.
|
||||
mail_location = mdbox:~/mdbox
|
||||
|
||||
# User and group used to store and access mailboxes.
|
||||
mail_uid = dovecot
|
||||
mail_gid = dovecot
|
||||
|
||||
# As we're using virtual mailboxes, the system user will be "dovecot", which
|
||||
# has uid in the 100-500 range. By default using uids <500 is blocked, so we
|
||||
# need to explicitly lower the value to allow storage of mail as "dovecot".
|
||||
first_valid_uid = 100
|
||||
first_valid_gid = 100
|
||||
|
||||
#
|
||||
# Authentication
|
||||
#
|
||||
|
||||
# Static file, in /data/dovecot/users.
|
||||
auth_mechanisms = plain
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = scheme=CRYPT username_format=%u /data/dovecot/users
|
||||
}
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = /data/dovecot/users
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# TLS
|
||||
#
|
||||
|
||||
# TLS is mandatory.
|
||||
# The entrypoint generates auto-ssl.conf, with all the certificates.
|
||||
ssl = required
|
||||
!include_try /etc/dovecot/auto-ssl.conf
|
||||
|
||||
# Only allow TLS 1.2 and up.
|
||||
ssl_min_protocol = TLSv1.2
|
||||
|
||||
|
||||
#
|
||||
# Protocols
|
||||
#
|
||||
protocols = lmtp imap pop3 sieve
|
||||
|
||||
#
|
||||
# IMAP
|
||||
#
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
# Disable plain text IMAP, just in case.
|
||||
port = 0
|
||||
}
|
||||
inet_listener imaps {
|
||||
port = 993
|
||||
ssl = yes
|
||||
}
|
||||
}
|
||||
|
||||
service imap {
|
||||
}
|
||||
|
||||
#
|
||||
# POP3
|
||||
#
|
||||
service pop3-login {
|
||||
inet_listener pop3 {
|
||||
# Disable plain text POP3, just in case.
|
||||
port = 0
|
||||
}
|
||||
inet_listener pop3s {
|
||||
port = 995
|
||||
ssl = yes
|
||||
}
|
||||
}
|
||||
|
||||
service pop3 {
|
||||
}
|
||||
|
||||
#
|
||||
# Sieve/managesieve
|
||||
#
|
||||
service managesieve-login {
|
||||
}
|
||||
service managesieve {
|
||||
}
|
||||
protocol sieve {
|
||||
}
|
||||
plugin {
|
||||
sieve = file:~/sieve;active=~/.dovecot.sieve
|
||||
}
|
||||
|
||||
#
|
||||
# Internal services
|
||||
#
|
||||
service auth {
|
||||
unix_listener auth-userdb {
|
||||
}
|
||||
|
||||
# Grant chasquid access to request user authentication.
|
||||
unix_listener auth-chasquid-userdb {
|
||||
mode = 0660
|
||||
user = chasquid
|
||||
}
|
||||
unix_listener auth-chasquid-client {
|
||||
mode = 0660
|
||||
user = chasquid
|
||||
}
|
||||
}
|
||||
service auth-worker {
|
||||
}
|
||||
dict {
|
||||
}
|
||||
service lmtp {
|
||||
# This is used by mda-lmtp.
|
||||
unix_listener lmtp {
|
||||
}
|
||||
}
|
||||
105
docker/entrypoint.sh
Executable file
105
docker/entrypoint.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Script that is used as a Docker entrypoint.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
if ! grep -q data /proc/mounts; then
|
||||
echo "/data is not mounted."
|
||||
echo "Check that the /data volume is set up correctly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create the directory structure if it's not there.
|
||||
# Some of these directories are symlink targets, see the Dockerfile.
|
||||
mkdir -p /data/chasquid
|
||||
mkdir -p /data/letsencrypt
|
||||
mkdir -p /data/chasquid
|
||||
mkdir -p /data/chasquid/domains
|
||||
mkdir -p /data/dovecot
|
||||
|
||||
# Set up the certificates for the requested domains.
|
||||
if [ "$AUTO_CERTS" != "" ]; then
|
||||
# If we were given an email to use for letsencrypt, use it. Otherwise
|
||||
# continue without one.
|
||||
MAIL_OPTS="--register-unsafely-without-email"
|
||||
if [ "$CERTS_MAIL" != "" ]; then
|
||||
MAIL_OPTS="-m $CERTS_MAIL"
|
||||
fi
|
||||
|
||||
for DOMAIN in $(echo $AUTO_CERTS); do
|
||||
# If it has never been set up, then do so.
|
||||
if ! [ -e /etc/letsencrypt/live/$DOMAIN/fullchain.pem ]; then
|
||||
certbot certonly \
|
||||
--non-interactive \
|
||||
--standalone \
|
||||
--agree-tos \
|
||||
$MAIL_OPTS \
|
||||
-d $DOMAIN
|
||||
else
|
||||
echo "$DOMAIN certificate already set up."
|
||||
fi
|
||||
done
|
||||
|
||||
# Renew on startup, since the container won't have cron facilities.
|
||||
# Note this requires you to restart every week or so, to make sure
|
||||
# your certificate does not expire.
|
||||
certbot renew
|
||||
fi
|
||||
|
||||
CERT_DOMAINS=""
|
||||
for i in $(ls /etc/letsencrypt/live/); do
|
||||
if [ -e "/etc/letsencrypt/live/$i/fullchain.pem" ]; then
|
||||
CERT_DOMAINS="$CERT_DOMAINS $i"
|
||||
fi
|
||||
done
|
||||
|
||||
# We need one domain to use as a default - pick the last one.
|
||||
ONE_DOMAIN=$i
|
||||
|
||||
# Check that there's at least once certificate at this point.
|
||||
if [ "$CERT_DOMAINS" == "" ]; then
|
||||
echo "No certificates found."
|
||||
echo
|
||||
echo "Set AUTO_CERTS='example.com' to automatically get one."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Give chasquid access to the certificates.
|
||||
# Dovecot does not need this as it reads them as root.
|
||||
setfacl -R -m u:chasquid:rX /etc/letsencrypt/{live,archive}
|
||||
|
||||
# Give chasquid access to the data directory.
|
||||
mkdir -p /data/chasquid/data
|
||||
chown -R chasquid /data/chasquid/
|
||||
|
||||
# Give dovecot access to the mailbox home.
|
||||
mkdir -p /data/mail/
|
||||
chown dovecot:dovecot /data/mail/
|
||||
|
||||
# Generate the dovecot ssl configuration based on all the certificates we have.
|
||||
# The default goes first because dovecot complains otherwise.
|
||||
echo "# Autogenerated by entrypoint.sh" > /etc/dovecot/auto-ssl.conf
|
||||
cat >> /etc/dovecot/auto-ssl.conf <<EOF
|
||||
ssl_cert = </etc/letsencrypt/live/$ONE_DOMAIN/fullchain.pem
|
||||
ssl_key = </etc/letsencrypt/live/$ONE_DOMAIN/privkey.pem
|
||||
EOF
|
||||
for DOMAIN in $CERT_DOMAINS; do
|
||||
echo "local_name $DOMAIN {"
|
||||
echo " ssl_cert = </etc/letsencrypt/live/$DOMAIN/fullchain.pem"
|
||||
echo " ssl_key = </etc/letsencrypt/live/$DOMAIN/privkey.pem"
|
||||
echo "}"
|
||||
done >> /etc/dovecot/auto-ssl.conf
|
||||
|
||||
# Pick the default domain as default hostname for chasquid. This is only used
|
||||
# in plain text sessions and on very rare cases, and it's mostly for aesthetic
|
||||
# purposes.
|
||||
echo "hostname: '$ONE_DOMAIN'" >> /etc/chasquid/chasquid.conf
|
||||
|
||||
|
||||
# Start the services: dovecot in background, chasquid in foreground.
|
||||
start-stop-daemon --start --quiet --pidfile /run/dovecot.pid \
|
||||
--exec /usr/sbin/dovecot -- -c /etc/dovecot/dovecot.conf
|
||||
|
||||
sudo -u chasquid -g chasquid /usr/bin/chasquid $CHASQUID_FLAGS
|
||||
1
docs/docker.md
Symbolic link
1
docs/docker.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../docker/README.md
|
||||
Reference in New Issue
Block a user